From: Olyutorskii Date: Wed, 24 Apr 2013 12:13:10 +0000 (+0900) Subject: 慣例情報更新 X-Git-Tag: fromMercurial~15 X-Git-Url: http://git.osdn.net/view?p=mikutoga%2FTogaGem.git;a=commitdiff_plain;h=7654dfe69b93bad86096a308b5b8f28d28d2f902 慣例情報更新 --- diff --git a/src/main/java/jp/sfjp/mikutoga/typical/AliasMap.java b/src/main/java/jp/sfjp/mikutoga/typical/AliasMap.java new file mode 100644 index 0000000..bd33ba7 --- /dev/null +++ b/src/main/java/jp/sfjp/mikutoga/typical/AliasMap.java @@ -0,0 +1,168 @@ +/* + * alias map with primary & global + * + * License : The MIT License + * Copyright(c) 2013 MikuToga Partners + */ + +package jp.sfjp.mikutoga.typical; + +import java.text.Normalizer; +import java.util.HashMap; +import java.util.Map; + +/** + * プライマリ名 - グローバル名間の対応、 + * およびUnicode正規化によるゆらぎ表記吸収処理を行う。 + * @param 別名管理クラス + * @see + * Unicode正規化 + * @see + * UNICODE NORMALIZATION FORMS + * @see java.text.Normalizer + */ +class AliasMap { + + private final Map primaryAliasMap; + private final Map globalAliasMap; + + /** + * コンストラクタ。 + */ + AliasMap(){ + super(); + + this.primaryAliasMap = new HashMap(); + this.globalAliasMap = new HashMap(); + + return; + } + + + /** + * NFKC正規化されたUnicode文字列を返す。 + *

等価な全半角、濁点、丸付き数字などの表現の正規化を目的とする。 + *

    + *
  • 「ボーン」は「ボーン」になる + *
  • 「ホ゛ーン9」は「ボーン9」になる + *
+ * @param name 正規化対象文字列 + * @return 正規化済み文字列 + */ + static String normalize(CharSequence name){ + String result; + result = Normalizer.normalize(name, Normalizer.Form.NFKC); + return result; + } + + + /** + * 別名管理オブジェクトを登録。 + *

キーとなる名前は、事前にNFKC正規化で + * 揺らぎ表記が吸収されたプライマリ名およびグローバル名。 + *

登録キーが衝突した時は後の方が有効となる。 + * @param alias 別名管理オブジェクト + */ + void addAlias(T alias){ + addPrimary(alias); + addGlobal(alias); + return; + } + + /** + * 別名管理オブジェクトと正規化プライマリ名を対応づける。 + *

事前にNFKC正規化されたプライマリ名が登録キーとなる。 + *

登録キーが衝突した時は後の方が有効となる。 + * @param alias 別名管理オブジェクト + */ + private void addPrimary(T alias){ + for(String primaryName : alias.getPrimaryNameList()){ + String normalized = normalize(primaryName); + normalized = normalized.intern(); + this.primaryAliasMap.put(normalized, alias); + } + return; + } + + /** + * 別名管理オブジェクトと正規化グローバル名を対応づける。 + *

事前にNFKC正規化されたグローバル名が登録キーとなる。 + *

登録キーが衝突した時は後の方が有効となる。 + * @param alias 別名管理オブジェクト + */ + private void addGlobal(T alias){ + for(String globalName : alias.getGlobalNameList()){ + String normalized = normalize(globalName); + normalized = normalized.intern(); + this.globalAliasMap.put(normalized, alias); + } + return; + } + + /** + * 名前から別名管理オブジェクトを得る。 + *

プライマリ名、グローバル名の順で検索される。 + *

名前は事前にNFKC正規化された後、検索キーとなる。 + * @param name 名前 + * @return 別名管理オブジェクト。見つからなければnull + */ + T getAlias(String name){ + T result; + result = getAliasByPrimary(name); + if(result == null){ + result = getAliasByGlobal(name); + } + return result; + } + + /** + * プライマリ名から別名管理オブジェクトを得る。 + *

プライマリ名は事前にNFKC正規化された後、検索キーとなる。 + * @param primaryName プライマリ名 + * @return 別名管理オブジェクト。見つからなければnull + */ + T getAliasByPrimary(String primaryName){ + String normalized = normalize(primaryName); + T result = this.primaryAliasMap.get(normalized); + return result; + } + + /** + * グローバル名から別名管理オブジェクトを得る。 + *

グローバル名は事前にNFKC正規化された後、検索キーとなる。 + * @param globalName グローバル名 + * @return 別名管理オブジェクト。見つからなければnull + */ + T getAliasByGlobal(String globalName){ + String normalized = normalize(globalName); + T result = this.globalAliasMap.get(normalized); + return result; + } + + /** + * プライマリ名から代表グローバル名を得る。 + *

プライマリ名は事前にNFKC正規化された後、検索キーとなる。 + * @param primaryName プライマリ名 + * @return 代表グローバル名。見つからなければnull + */ + String primary2global(String primaryName){ + T alias = getAliasByPrimary(primaryName); + if(alias == null) return null; + String globalName = alias.getTopGlobalName(); + return globalName; + } + + /** + * グローバル名から代表プライマリ名を得る。 + *

グローバル名は事前にNFKC正規化された後、検索キーとなる。 + * @param globalName グローバル名 + * @return 代表プライマリ名。見つからなければnull + */ + String global2primary(String globalName){ + T alias = getAliasByGlobal(globalName); + if(alias == null) return null; + String primary = alias.getTopPrimaryName(); + return primary; + } + +} diff --git a/src/main/java/jp/sourceforge/mikutoga/typical/I18nAlias.java b/src/main/java/jp/sfjp/mikutoga/typical/I18nAlias.java similarity index 63% rename from src/main/java/jp/sourceforge/mikutoga/typical/I18nAlias.java rename to src/main/java/jp/sfjp/mikutoga/typical/I18nAlias.java index 0f37ced..032612e 100644 --- a/src/main/java/jp/sourceforge/mikutoga/typical/I18nAlias.java +++ b/src/main/java/jp/sfjp/mikutoga/typical/I18nAlias.java @@ -5,11 +5,10 @@ * Copyright(c) 2011 MikuToga Partners */ -package jp.sourceforge.mikutoga.typical; +package jp.sfjp.mikutoga.typical; import java.io.IOException; import java.io.InputStream; -import java.text.Normalizer; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -24,10 +23,15 @@ import org.xml.sax.SAXException; /** * 国際化&別名管理オブジェクトの実装基板。 + *

別名管理オブジェクトは、 + * 各々のリストの先頭が代表名となる、 + * プライマリ名の不変リストとグローバル名の不変リストを持つ。 *

国産モデルではプライマリ名に日本語名が収められることが多い。 * プライマリ名は必ず一つ以上なければならない。 *

国産モデルではグローバル名に英語名が収められることが多いが、 * プライマリ名と同一の日本語名が収められている場合も多い。 + *

別名管理オブジェクトは、 + * インスタンス間での順序を定義するためのオーダー番号を持つ。 */ class I18nAlias { @@ -38,39 +42,39 @@ class I18nAlias { private int orderNo; - private final List primaryList; - private final List globalList; + private final List primaryNameList; + private final List globalNameList; - private final List umodPrimaryList; - private final List umodGlobalList; + private final List umodPrimaryNameList; + private final List umodGlobalNameList; /** * コンストラクタ。 *

各初期数が0以下の場合は、 * 状況に応じて伸長する連結リストが用意される。 - * @param primaryNo プライマリ名初期数。 - * @param globalNo グローバル名初期数。 + * @param primaryNum プライマリ名初期数。 + * @param globalNum グローバル名初期数。 */ - protected I18nAlias(int primaryNo, int globalNo){ + protected I18nAlias(int primaryNum, int globalNum){ super(); - if(primaryNo <= 0){ - this.primaryList = new LinkedList(); + if(primaryNum <= 0){ + this.primaryNameList = new LinkedList(); }else{ - this.primaryList = new ArrayList(primaryNo); + this.primaryNameList = new ArrayList(primaryNum); } - if(globalNo <= 0){ - this.globalList = new LinkedList(); + if(globalNum <= 0){ + this.globalNameList = new LinkedList(); }else{ - this.globalList = new ArrayList(globalNo); + this.globalNameList = new ArrayList(globalNum); } - this.umodPrimaryList = - Collections.unmodifiableList(this.primaryList); - this.umodGlobalList = - Collections.unmodifiableList(this.globalList); + this.umodPrimaryNameList = + Collections.unmodifiableList(this.primaryNameList); + this.umodGlobalNameList = + Collections.unmodifiableList(this.globalNameList); return; } @@ -98,7 +102,6 @@ class I18nAlias { throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory factory; factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(is); @@ -108,21 +111,6 @@ class I18nAlias { return top; } - /** - * NFKC正規化された文字列を返す。 - *

    - *
  • 「ボーン」は「ボーン」になる - *
  • 「ホ゛ーン9」は「ボーン9」になる - *
- * @param name 正規化対象文字列 - * @return 正規化済み文字列 - */ - protected static String normalize(CharSequence name){ - String result; - result = Normalizer.normalize(name, Normalizer.Form.NFKC); - return result; - } - /** * オーダー番号を返す。 @@ -143,10 +131,11 @@ class I18nAlias { /** * プライマリ名の代表をひとつ返す。 + *

必ず存在しなければならない。 * @return 最初のプライマリ名 */ public String getTopPrimaryName(){ - String result = this.primaryList.get(0); + String result = this.primaryNameList.get(0); return result; } @@ -155,43 +144,44 @@ class I18nAlias { * @return 最初のグローバル名。ひとつもなければnull */ public String getTopGlobalName(){ - String result; - if(this.globalList.isEmpty()) result = null; - else result = this.globalList.get(0); + if(this.globalNameList.isEmpty()) return null; + + String result = this.globalNameList.get(0); + return result; } /** - * プライマリ名の全エイリアス文字列リストを返す。 - * @return 全プライマリ名リスト。(不可変) + * プライマリ名の全別名リストを返す。 + * @return 全別名リスト。(不可変) */ - public List getPrimaryList(){ - return this.umodPrimaryList; + public List getPrimaryNameList(){ + return this.umodPrimaryNameList; } /** * プライマリ名を追加。 - * @param name プライマリ名 + * @param primaryName プライマリ名 */ - protected void addPrimaryName(String name){ - this.primaryList.add(name); + protected void addPrimaryName(String primaryName){ + this.primaryNameList.add(primaryName); return; } /** - * グローバル名の全エイリアス文字列リストを返す。 - * @return 全グローバル名リスト。(不可変) + * グローバル名の全別名リストを返す。 + * @return 全別名リスト。(不可変) */ - public List getGlobalList(){ - return this.umodGlobalList; + public List getGlobalNameList(){ + return this.umodGlobalNameList; } /** * グローバル名を追加。 - * @param name グローバル名 + * @param globalName グローバル名 */ - protected void addGlobalName(String name){ - this.globalList.add(name); + protected void addGlobalName(String globalName){ + this.globalNameList.add(globalName); return; } diff --git a/src/main/java/jp/sourceforge/mikutoga/typical/TypicalBone.java b/src/main/java/jp/sfjp/mikutoga/typical/TypicalBone.java similarity index 56% rename from src/main/java/jp/sourceforge/mikutoga/typical/TypicalBone.java rename to src/main/java/jp/sfjp/mikutoga/typical/TypicalBone.java index 4f907e8..d97a752 100644 --- a/src/main/java/jp/sourceforge/mikutoga/typical/TypicalBone.java +++ b/src/main/java/jp/sfjp/mikutoga/typical/TypicalBone.java @@ -5,15 +5,13 @@ * Copyright(c) 2011 MikuToga Partners */ -package jp.sourceforge.mikutoga.typical; +package jp.sfjp.mikutoga.typical; import java.io.IOException; import java.io.InputStream; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedList; import java.util.List; -import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -31,18 +29,23 @@ public final class TypicalBone extends I18nAlias { private static final Class THISCLASS = TypicalBone.class; private static final String BONE_XML = "resources/typicalBone.xml"; - private static final List TYP_BONE_LIST = + private static final String ELEM_BONE = "bone"; + private static final String ELEM_ROOT = "root"; + private static final String ELEM_PRIMARY = "primary"; + private static final String ELEM_GLOBAL = "global"; + private static final String ATTR_NAME = "name"; + + private static final List BONE_LIST = new LinkedList(); - private static final Map PRIMARY_MAP = - new HashMap(); - private static final Map GLOBAL_MAP = - new HashMap(); + private static final AliasMap BONE_ALIAS_MAP = + new AliasMap(); - private static final List TYP_BONE_UNMODLIST = - Collections.unmodifiableList(TYP_BONE_LIST); + private static final List BONE_UNMODLIST = + Collections.unmodifiableList(BONE_LIST); static{ InputStream is = THISCLASS.getResourceAsStream(BONE_XML); + Element top; try{ top = I18nAlias.loadXml(is); @@ -60,15 +63,19 @@ public final class TypicalBone extends I18nAlias { } + private boolean isRoot; + + /** * コンストラクタ。 *

各初期数が0以下の場合は、 * 状況に応じて伸長する連結リストが用意される。 - * @param primaryNo プライマリ名初期数。 - * @param globalNo グローバル名初期数。 + * @param primaryNum プライマリ名初期数。 + * @param globalNum グローバル名初期数。 */ - private TypicalBone(int primaryNo, int globalNo){ - super(primaryNo, globalNo); + private TypicalBone(int primaryNum, int globalNum){ + super(primaryNum, globalNum); + assert this.getClass() == THISCLASS; return; } @@ -78,12 +85,13 @@ public final class TypicalBone extends I18nAlias { * @param top 最上位要素 */ private static void parse(Element top) { - NodeList boneList = top.getElementsByTagName("bone"); + NodeList boneList = top.getElementsByTagName(ELEM_BONE); int boneNo = boneList.getLength(); for(int idx = 0; idx < boneNo; idx++){ - Element bone = (Element) boneList.item(idx); - TypicalBone typBone = parseBone(bone); - TYP_BONE_LIST.add(typBone); + Element boneElem = (Element) boneList.item(idx); + TypicalBone typBone = parseBone(boneElem); + BONE_LIST.add(typBone); + BONE_ALIAS_MAP.addAlias(typBone); } return; @@ -91,49 +99,48 @@ public final class TypicalBone extends I18nAlias { /** * bone要素を解読する。 - * @param bone bone要素 + * @param boneElem bone要素 * @return ボーン情報 */ - private static TypicalBone parseBone(Element bone){ - NodeList primaryNodes = bone.getElementsByTagName("primary"); - NodeList globalNodes = bone.getElementsByTagName("global"); + private static TypicalBone parseBone(Element boneElem){ + NodeList primaryNodes = boneElem.getElementsByTagName(ELEM_PRIMARY); + NodeList globalNodes = boneElem.getElementsByTagName(ELEM_GLOBAL); int primaryNo = primaryNodes.getLength(); int globalNo = globalNodes.getLength(); - TypicalBone typBone = new TypicalBone(primaryNo, globalNo); + assert primaryNo > 0; + + TypicalBone bone = new TypicalBone(primaryNo, globalNo); for(int idx = 0; idx < primaryNo; idx++){ - Element primary = (Element) primaryNodes.item(idx); - String name = primary.getAttribute("name"); - typBone.addPrimaryName(name); + Element primaryElem = (Element) primaryNodes.item(idx); + String name = primaryElem.getAttribute(ATTR_NAME); + bone.addPrimaryName(name); } for(int idx = 0; idx < globalNo; idx++){ - Element global = (Element) globalNodes.item(idx); - String name = global.getAttribute("name"); - typBone.addGlobalName(name); - } - - for(String primaryName : typBone.getPrimaryList()){ - String key = normalize(primaryName).intern(); - PRIMARY_MAP.put(key, typBone); + Element globalElem = (Element) globalNodes.item(idx); + String name = globalElem.getAttribute(ATTR_NAME); + bone.addGlobalName(name); } - for(String globalName : typBone.getGlobalList()){ - String key = normalize(globalName).intern(); - GLOBAL_MAP.put(key, typBone); + NodeList rootNodes = boneElem.getElementsByTagName(ELEM_ROOT); + if(rootNodes.getLength() > 0){ + bone.isRoot = true; + }else{ + bone.isRoot = false; } - return typBone; + return bone; } /** - * 全ボーン情報を一意に順序付ける設定を行う。 + * 全ボーン情報に通し番号を付ける。 *

XMLでの定義順が反映される。 */ private static void numbering(){ int order = 0; - for(TypicalBone bone : TYP_BONE_LIST){ + for(TypicalBone bone : BONE_LIST){ bone.setOrderNo(order++); } @@ -144,8 +151,8 @@ public final class TypicalBone extends I18nAlias { * 全ボーンの不変リストを返す。 * @return 全ボーンのリスト */ - public static List getBoneList(){ - return TYP_BONE_UNMODLIST; + public static List getTypicalBoneList(){ + return BONE_UNMODLIST; } /** @@ -155,8 +162,7 @@ public final class TypicalBone extends I18nAlias { * @return モーフ情報。見つからなければnull */ public static TypicalBone findWithPrimary(String primaryName){ - String key = normalize(primaryName); - TypicalBone result = PRIMARY_MAP.get(key); + TypicalBone result = BONE_ALIAS_MAP.getAliasByPrimary(primaryName); return result; } @@ -167,8 +173,7 @@ public final class TypicalBone extends I18nAlias { * @return モーフ情報。見つからなければnull */ public static TypicalBone findWithGlobal(String globalName){ - String key = normalize(globalName); - TypicalBone result = GLOBAL_MAP.get(key); + TypicalBone result = BONE_ALIAS_MAP.getAliasByGlobal(globalName); return result; } @@ -178,10 +183,8 @@ public final class TypicalBone extends I18nAlias { * @return グローバル名。見つからなければnull */ public static String primary2global(String primaryName){ - TypicalBone bone = findWithPrimary(primaryName); - if(bone == null) return null; - String global = bone.getTopGlobalName(); - return global; + String globalName = BONE_ALIAS_MAP.primary2global(primaryName); + return globalName; } /** @@ -190,10 +193,19 @@ public final class TypicalBone extends I18nAlias { * @return プライマリ名。見つからなければnull */ public static String global2primary(String globalName){ - TypicalBone bone = findWithGlobal(globalName); - if(bone == null) return null; - String primary = bone.getTopPrimaryName(); - return primary; + String primaryName = BONE_ALIAS_MAP.global2primary(globalName); + return primaryName; + } + + + /** + * このボーンが親を持たないルートボーンとして扱われる慣習なのか + * 判定する。 + *

※「全親」ボーンに関する慣習は無視される。 + * @return 親を持たなければtrue + */ + public boolean isRoot(){ + return this.isRoot; } } diff --git a/src/main/java/jp/sourceforge/mikutoga/typical/TypicalMorph.java b/src/main/java/jp/sfjp/mikutoga/typical/TypicalMorph.java similarity index 63% rename from src/main/java/jp/sourceforge/mikutoga/typical/TypicalMorph.java rename to src/main/java/jp/sfjp/mikutoga/typical/TypicalMorph.java index c03aa74..2581ac3 100644 --- a/src/main/java/jp/sourceforge/mikutoga/typical/TypicalMorph.java +++ b/src/main/java/jp/sfjp/mikutoga/typical/TypicalMorph.java @@ -5,14 +5,13 @@ * Copyright(c) 2011 MikuToga Partners */ -package jp.sourceforge.mikutoga.typical; +package jp.sfjp.mikutoga.typical; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; -import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; @@ -34,19 +33,25 @@ public final class TypicalMorph extends I18nAlias { private static final Class THISCLASS = TypicalMorph.class; private static final String MORPH_XML = "resources/typicalMorph.xml"; + private static final String ELEM_MORPHGROUP = "morphGroup"; + private static final String ELEM_MORPH = "morph"; + private static final String ATTR_TYPE = "type"; + private static final String ELEM_PRIMARY = "primary"; + private static final String ELEM_GLOBAL = "global"; + private static final String ATTR_NAME = "name"; + private static final List EMPTY = Collections.emptyList(); private static final Map> TYPED_MAP = new EnumMap>(MorphType.class); - private static final Map PRIMARY_MAP = - new HashMap(); - private static final Map GLOBAL_MAP = - new HashMap(); + private static final AliasMap MORPH_ALIAS_MAP = + new AliasMap(); static{ InputStream is = THISCLASS.getResourceAsStream(MORPH_XML); + Element top; try{ top = I18nAlias.loadXml(is); @@ -72,12 +77,16 @@ public final class TypicalMorph extends I18nAlias { *

各初期数が0以下の場合は、 * 状況に応じて伸長する連結リストが用意される。 * @param type モーフ種別 - * @param primaryNo プライマリ名初期数。 - * @param globalNo グローバル名初期数。 + * @param primaryNum プライマリ名初期数。 + * @param globalNum グローバル名初期数。 */ - private TypicalMorph(MorphType type, int primaryNo, int globalNo){ - super(primaryNo, globalNo); + private TypicalMorph(MorphType type, int primaryNum, int globalNum){ + super(primaryNum, globalNum); + this.type = type; + + assert this.getClass() == THISCLASS; + return; } @@ -87,11 +96,11 @@ public final class TypicalMorph extends I18nAlias { * @param top 最上位要素 */ private static void parse(Element top) { - NodeList groupList = top.getElementsByTagName("morphGroup"); + NodeList groupList = top.getElementsByTagName(ELEM_MORPHGROUP); int groupNo = groupList.getLength(); for(int idx = 0; idx < groupNo; idx++){ - Element group = (Element) groupList.item(idx); - parseGroup(group); + Element groupElem = (Element) groupList.item(idx); + parseGroup(groupElem); } // 必要に応じモーフ枠に不変空リスト登録 @@ -106,21 +115,22 @@ public final class TypicalMorph extends I18nAlias { /** * モーフグループ構造を解読する。 - * @param group morphGroup要素 + * @param groupElem morphGroup要素 */ - private static void parseGroup(Element group){ - String typeAttr = group.getAttribute("type"); + private static void parseGroup(Element groupElem){ + String typeAttr = groupElem.getAttribute(ATTR_TYPE); MorphType morphType = MorphType.valueOf(typeAttr); - NodeList morphList = group.getElementsByTagName("morph"); + NodeList morphList = groupElem.getElementsByTagName(ELEM_MORPH); int morphNo = morphList.getLength(); List groupedList = new ArrayList(morphNo); for(int idx = 0; idx < morphNo; idx++){ - Element morph = (Element) morphList.item(idx); - TypicalMorph common = parseMorph(morph, morphType); - groupedList.add(common); + Element morphElem = (Element) morphList.item(idx); + TypicalMorph morph = parseMorph(morphElem, morphType); + groupedList.add(morph); + MORPH_ALIAS_MAP.addAlias(morph); } groupedList = Collections.unmodifiableList(groupedList); @@ -131,65 +141,58 @@ public final class TypicalMorph extends I18nAlias { /** * morph要素を解読する。 - * @param morph morph要素 + * @param morphElem morph要素 * @param mtype モーフ種別 * @return モーフ情報 */ - private static TypicalMorph parseMorph(Element morph, MorphType mtype){ - NodeList primaryNodes = morph.getElementsByTagName("primary"); - NodeList globalNodes = morph.getElementsByTagName("global"); + private static TypicalMorph parseMorph(Element morphElem, + MorphType mtype ){ + NodeList primaryNodes = morphElem.getElementsByTagName(ELEM_PRIMARY); + NodeList globalNodes = morphElem.getElementsByTagName(ELEM_GLOBAL); int primaryNo = primaryNodes.getLength(); int globalNo = globalNodes.getLength(); - TypicalMorph typMorph = new TypicalMorph(mtype, primaryNo, globalNo); + assert primaryNo > 0; + + TypicalMorph morph = new TypicalMorph(mtype, primaryNo, globalNo); for(int idx = 0; idx < primaryNo; idx++){ - Element primary = (Element) primaryNodes.item(idx); - String name = primary.getAttribute("name"); - typMorph.addPrimaryName(name); + Element primaryElem = (Element) primaryNodes.item(idx); + String primaryName = primaryElem.getAttribute(ATTR_NAME); + morph.addPrimaryName(primaryName); } for(int idx = 0; idx < globalNo; idx++){ - Element global = (Element) globalNodes.item(idx); - String name = global.getAttribute("name"); - typMorph.addGlobalName(name); + Element globalElem = (Element) globalNodes.item(idx); + String globalName = globalElem.getAttribute(ATTR_NAME); + morph.addGlobalName(globalName); } - for(String primaryName : typMorph.getPrimaryList()){ - String key = normalize(primaryName).intern(); - PRIMARY_MAP.put(key, typMorph); - } - - for(String globalName : typMorph.getGlobalList()){ - String key = normalize(globalName).intern(); - GLOBAL_MAP.put(key, typMorph); - } - - return typMorph; + return morph; } /** - * 全モーフ情報を一意に順序付ける設定を行う。 + * 全モーフ情報に通し番号を付ける。 *

同一グループ内ではXMLでの定義順が反映される。 */ private static void numbering(){ int order = 0; for(MorphType morphType : MorphType.values()){ - for(TypicalMorph common : TYPED_MAP.get(morphType)){ - common.setOrderNo(order++); + for(TypicalMorph morph : TYPED_MAP.get(morphType)){ + morph.setOrderNo(order++); } } return; } - /** - * 種別ごとのモーフ情報リストを取得する。 + * 種別ごとのモーフ情報不変リストを取得する。 * @param morphType モーフ種別 - * @return モーフ情報リスト + * @return モーフ情報不変リスト */ - public static List getTypedMorphList(MorphType morphType){ + public static List getTypicalMorphList( + MorphType morphType ){ List result = TYPED_MAP.get(morphType); return result; } @@ -201,8 +204,7 @@ public final class TypicalMorph extends I18nAlias { * @return モーフ情報。見つからなければnull */ public static TypicalMorph findWithPrimary(String primaryName){ - String key = normalize(primaryName); - TypicalMorph result = PRIMARY_MAP.get(key); + TypicalMorph result = MORPH_ALIAS_MAP.getAliasByPrimary(primaryName); return result; } @@ -213,8 +215,7 @@ public final class TypicalMorph extends I18nAlias { * @return モーフ情報。見つからなければnull */ public static TypicalMorph findWithGlobal(String globalName){ - String key = normalize(globalName); - TypicalMorph result = GLOBAL_MAP.get(key); + TypicalMorph result = MORPH_ALIAS_MAP.getAliasByGlobal(globalName); return result; } @@ -224,10 +225,8 @@ public final class TypicalMorph extends I18nAlias { * @return グローバル名。見つからなければnull */ public static String primary2global(String primaryName){ - TypicalMorph morph = findWithPrimary(primaryName); - if(morph == null) return null; - String global = morph.getTopGlobalName(); - return global; + String globalName = MORPH_ALIAS_MAP.primary2global(primaryName); + return globalName; } /** @@ -236,10 +235,8 @@ public final class TypicalMorph extends I18nAlias { * @return プライマリ名。見つからなければnull */ public static String global2primary(String globalName){ - TypicalMorph morph = findWithGlobal(globalName); - if(morph == null) return null; - String primary = morph.getTopPrimaryName(); - return primary; + String primaryName = MORPH_ALIAS_MAP.global2primary(globalName); + return primaryName; } diff --git a/src/main/java/jp/sfjp/mikutoga/typical/UniqBone.java b/src/main/java/jp/sfjp/mikutoga/typical/UniqBone.java new file mode 100644 index 0000000..af171c5 --- /dev/null +++ b/src/main/java/jp/sfjp/mikutoga/typical/UniqBone.java @@ -0,0 +1,70 @@ +/* + * unique bone information + * + * License : The MIT License + * Copyright(c) 2013 MikuToga Partners + */ + +package jp.sfjp.mikutoga.typical; + +/** + * 特別扱いされるボーンに関する諸々。 + */ +public final class UniqBone { + + private static final String HIDARI_LEFT = "\u5de6"; // 「左」 + private static final String MIGI_RIGHT = "\u53f3"; // 「右」 + private static final String HIZA_KNEE = "\u3072\u3056"; // 「ひざ」 + + + private static final String KNEE_L_PFX = + HIDARI_LEFT + HIZA_KNEE; // 左ひざ + private static final String KNEE_R_PFX = + MIGI_RIGHT + HIZA_KNEE; // 右ひざ + + static{ + assert "左ひざ".equals(KNEE_L_PFX); + assert "右ひざ".equals(KNEE_R_PFX); + } + + + /** + * 隠しコンストラクタ。 + */ + private UniqBone(){ + assert false; + throw new AssertionError(); + } + + /** + * IK演算時の回転方向に制限を受ける「ひざボーン」か否か、 + * ボーン名で判定する。 + *

ボーンのプライマリ名が「左ひざ」もしくは「右ひざ」で始まれば + * ひざボーンとする。 + *

ひざボーン名の例 + *

    + *
  • 「左ひざ」 + *
  • 「左ひざげり」 + *
+ *

ひざボーン名ではない例 + *

    + *
  • 「左ひ」 + *
  • 「ひざ」 + *
  • 「前ひざ」 + *
  • 「左ひさ゛」 + *
  • 「左ヒザ」 + *
  • 「左ヒザ」 + *
  • 「左膝」 + *
  • 「Knee_L」 + *
+ * @param boneNameJp プライマリボーン名 + * @return ひざボーンならtrue + */ + public static boolean isPrimaryKneeName(String boneNameJp){ + if(boneNameJp.startsWith(KNEE_L_PFX)) return true; + if(boneNameJp.startsWith(KNEE_R_PFX)) return true; + + return false; + } + +} diff --git a/src/main/java/jp/sourceforge/mikutoga/typical/package-info.java b/src/main/java/jp/sfjp/mikutoga/typical/package-info.java similarity index 72% rename from src/main/java/jp/sourceforge/mikutoga/typical/package-info.java rename to src/main/java/jp/sfjp/mikutoga/typical/package-info.java index 93e1b33..128bf08 100644 --- a/src/main/java/jp/sourceforge/mikutoga/typical/package-info.java +++ b/src/main/java/jp/sfjp/mikutoga/typical/package-info.java @@ -7,10 +7,10 @@ /** * MMDコミュニティにおける一般的な慣例に関する情報を提供する。 - *

例)ボーン名やモーフ名の一般的な名前など + *

例)ボーン名やモーフ名の一般的な名前、対訳など *

MikuMikuDance Ver.7.39同梱のモデルなどが主な情報源。 */ -package jp.sourceforge.mikutoga.typical; +package jp.sfjp.mikutoga.typical; /* EOF */ diff --git a/src/main/resources/jp/sourceforge/mikutoga/typical/resources/typicalBone.xml b/src/main/resources/jp/sfjp/mikutoga/typical/resources/typicalBone.xml similarity index 96% rename from src/main/resources/jp/sourceforge/mikutoga/typical/resources/typicalBone.xml rename to src/main/resources/jp/sfjp/mikutoga/typical/resources/typicalBone.xml index dbd26fb..9bf6896 100644 --- a/src/main/resources/jp/sourceforge/mikutoga/typical/resources/typicalBone.xml +++ b/src/main/resources/jp/sfjp/mikutoga/typical/resources/typicalBone.xml @@ -9,7 +9,8 @@ - + + @@ -22,6 +23,7 @@ + @@ -312,11 +314,13 @@ + + @@ -332,76 +336,91 @@ + + + + + + + + + + + + + + + diff --git a/src/main/resources/jp/sourceforge/mikutoga/typical/resources/typicalMorph.xml b/src/main/resources/jp/sfjp/mikutoga/typical/resources/typicalMorph.xml similarity index 100% rename from src/main/resources/jp/sourceforge/mikutoga/typical/resources/typicalMorph.xml rename to src/main/resources/jp/sfjp/mikutoga/typical/resources/typicalMorph.xml diff --git a/src/test/java/jp/sfjp/mikutoga/typical/AliasMapTest.java b/src/test/java/jp/sfjp/mikutoga/typical/AliasMapTest.java new file mode 100644 index 0000000..bd6e637 --- /dev/null +++ b/src/test/java/jp/sfjp/mikutoga/typical/AliasMapTest.java @@ -0,0 +1,157 @@ +/* + */ + +package jp.sfjp.mikutoga.typical; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + */ +public class AliasMapTest { + + public AliasMapTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + static class TestAlias extends I18nAlias{ + } + + /** + * Test of normalize method, of class AliasMap. + */ + @Test + public void testNormalize() { + System.out.println("normalize"); + + assertEquals("", AliasMap.normalize("")); + + assertEquals("azAZ", AliasMap.normalize("azAZ")); + assertEquals("azAZ", AliasMap.normalize("azAZ")); + + assertEquals("5678", AliasMap.normalize("5678")); + assertEquals("56VII八", AliasMap.normalize("5⑥Ⅶ八")); + + assertEquals("ガ", AliasMap.normalize("ガ")); + assertEquals("ガ", AliasMap.normalize("ガ")); + assertEquals("ガ", AliasMap.normalize("ガ")); + assertEquals("ガ", AliasMap.normalize("カ"+"\u3099")); + assertEquals("カ\u0020\u3099", AliasMap.normalize("カ"+"\u309b")); + + assertEquals("パ", AliasMap.normalize("パ")); + assertEquals("パ", AliasMap.normalize("パ")); + assertEquals("パ", AliasMap.normalize("パ")); + assertEquals("パ", AliasMap.normalize("ハ"+"\u309a")); + assertEquals("ハ\u0020\u309a", AliasMap.normalize("ハ"+"\u309c")); + + assertEquals("リットル", AliasMap.normalize("㍑")); + + return; + } + + /** + * Test of addAlias method, of class AliasMap. + */ + @Test + public void testAddAlias() { + System.out.println("addAlias"); + + AliasMap map; + TestAlias alias1; + + alias1 = new TestAlias(); + alias1.addPrimaryName("p1"); + alias1.addPrimaryName("p2"); + alias1.addGlobalName("g1"); + alias1.addGlobalName("g2"); + + map = new AliasMap(); + map.addAlias(alias1); + + assertSame(alias1, map.getAlias("p1")); + assertSame(alias1, map.getAlias("p2")); + assertSame(alias1, map.getAlias("g1")); + assertSame(alias1, map.getAlias("g2")); + + assertNull(map.getAlias("ZZZ")); + + assertEquals("g1", map.primary2global("p2")); + assertEquals("p1", map.global2primary("g2")); + + assertNull(map.primary2global("ZZZ")); + assertNull(map.global2primary("ZZZ")); + + + TestAlias aliasHand; + TestAlias aliasFoot; + + aliasHand = new TestAlias(); + aliasHand.addPrimaryName("手1"); + aliasHand.addPrimaryName("おてて2"); + aliasHand.addPrimaryName("h"); + aliasHand.addPrimaryName("bone"); + aliasHand.addPrimaryName("cross1"); + aliasHand.addGlobalName("hand1"); + aliasHand.addGlobalName("paw2"); + aliasHand.addGlobalName("h"); + aliasHand.addGlobalName("bone"); + aliasHand.addGlobalName("cross2"); + + aliasFoot = new TestAlias(); + aliasFoot.addPrimaryName("足1"); + aliasFoot.addPrimaryName("あんよ2"); + aliasFoot.addPrimaryName("f"); + aliasFoot.addPrimaryName("bone"); + aliasFoot.addPrimaryName("cross2"); + aliasFoot.addGlobalName("foot1"); + aliasFoot.addGlobalName("hoof2"); + aliasFoot.addGlobalName("f"); + aliasFoot.addGlobalName("bone"); + aliasFoot.addGlobalName("cross1"); + + map = new AliasMap(); + map.addAlias(aliasHand); + map.addAlias(aliasFoot); + + assertSame(aliasHand, map.getAlias("h")); + assertSame(aliasFoot, map.getAlias("f")); + assertSame(aliasFoot, map.getAlias("bone")); + assertSame(aliasHand, map.getAlias("cross1")); + assertSame(aliasFoot, map.getAlias("cross2")); + + assertSame(aliasHand, map.getAliasByPrimary("h")); + assertSame(aliasFoot, map.getAliasByPrimary("f")); + assertSame(aliasFoot, map.getAliasByPrimary("bone")); + assertSame(aliasHand, map.getAliasByPrimary("cross1")); + assertSame(aliasFoot, map.getAliasByPrimary("cross2")); + + assertSame(aliasHand, map.getAliasByGlobal("h")); + assertSame(aliasFoot, map.getAliasByGlobal("f")); + assertSame(aliasFoot, map.getAliasByGlobal("bone")); + assertSame(aliasFoot, map.getAliasByGlobal("cross1")); + assertSame(aliasHand, map.getAliasByGlobal("cross2")); + + return; + } + +} diff --git a/src/test/java/jp/sfjp/mikutoga/typical/I18nAliasTest.java b/src/test/java/jp/sfjp/mikutoga/typical/I18nAliasTest.java new file mode 100644 index 0000000..ac37bb1 --- /dev/null +++ b/src/test/java/jp/sfjp/mikutoga/typical/I18nAliasTest.java @@ -0,0 +1,101 @@ +/* + */ + +package jp.sfjp.mikutoga.typical; + +import java.util.List; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + */ +public class I18nAliasTest { + + public I18nAliasTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of orderNo method, of class I18nAlias. + */ + @Test + public void testOrderNo() { + System.out.println("OrderNo"); + + I18nAlias alias = new I18nAlias(); + + assertEquals(0, alias.getOrderNo()); + alias.setOrderNo(99); + assertEquals(99, alias.getOrderNo()); + + I18nAlias alias2 = new I18nAlias(); + + alias2.setOrderNo(999); + assertTrue(0 > I18nAlias.ORDER_COMPARATOR.compare(alias, alias2)); + + alias2.setOrderNo(99); + assertTrue(0 == I18nAlias.ORDER_COMPARATOR.compare(alias, alias2)); + + alias2.setOrderNo(9); + assertTrue(0 < I18nAlias.ORDER_COMPARATOR.compare(alias, alias2)); + + return; + } + + /** + * Test of method, of class I18nAlias. + */ + @Test + public void testName() { + System.out.println("Name"); + + I18nAlias alias; + + alias = new I18nAlias(); + + alias.addPrimaryName("p1"); + + assertEquals("p1", alias.getTopPrimaryName()); + assertNull(alias.getTopGlobalName()); + + List primaryList; + List globalList; + + primaryList = alias.getPrimaryNameList(); + globalList = alias.getGlobalNameList(); + + assertEquals(1, primaryList.size()); + assertEquals(0, globalList.size()); + assertEquals("p1", primaryList.get(0)); + + alias.addGlobalName("g1"); + + assertEquals("g1", alias.getTopGlobalName()); + globalList = alias.getGlobalNameList(); + assertEquals(1, globalList.size()); + assertEquals("g1", globalList.get(0)); + + return; + } + +} diff --git a/src/test/java/jp/sourceforge/mikutoga/typical/TypicalBoneTest.java b/src/test/java/jp/sfjp/mikutoga/typical/TypicalBoneTest.java similarity index 74% rename from src/test/java/jp/sourceforge/mikutoga/typical/TypicalBoneTest.java rename to src/test/java/jp/sfjp/mikutoga/typical/TypicalBoneTest.java index e27d84f..877cfb0 100644 --- a/src/test/java/jp/sourceforge/mikutoga/typical/TypicalBoneTest.java +++ b/src/test/java/jp/sfjp/mikutoga/typical/TypicalBoneTest.java @@ -1,7 +1,7 @@ /* */ -package jp.sourceforge.mikutoga.typical; +package jp.sfjp.mikutoga.typical; import java.util.List; import org.junit.After; @@ -47,10 +47,10 @@ public class TypicalBoneTest { assertEquals("頭", result.getTopPrimaryName()); assertEquals("head", result.getTopGlobalName()); - assertEquals(1, result.getPrimaryList().size()); - assertEquals("頭", result.getPrimaryList().get(0)); - assertEquals(1, result.getGlobalList().size()); - assertEquals("head", result.getGlobalList().get(0)); + assertEquals(1, result.getPrimaryNameList().size()); + assertEquals("頭", result.getPrimaryNameList().get(0)); + assertEquals(1, result.getGlobalNameList().size()); + assertEquals("head", result.getGlobalNameList().get(0)); return; } @@ -122,7 +122,7 @@ public class TypicalBoneTest { } /** - * Test of getBoneList method, of class TypicalBone. + * Test of getTypicalBoneList method, of class TypicalBone. */ @Test public void testGetBoneList() { @@ -130,7 +130,7 @@ public class TypicalBoneTest { List boneList; - boneList = TypicalBone.getBoneList(); + boneList = TypicalBone.getTypicalBoneList(); assertNotNull(boneList); assertEquals(77, boneList.size()); @@ -147,4 +147,31 @@ public class TypicalBoneTest { return; } + /** + * Test of isRoot method, of class TypicalBone. + */ + @Test + public void testIsRoot() { + System.out.println("isRoot"); + + TypicalBone bone; + + bone = TypicalBone.findWithPrimary("センター"); + assertTrue(bone.isRoot()); + + bone = TypicalBone.findWithPrimary("頭"); + assertFalse(bone.isRoot()); + + bone = TypicalBone.findWithPrimary("右足IK"); + assertTrue(bone.isRoot()); + + bone = TypicalBone.findWithPrimary("左足IK"); + assertTrue(bone.isRoot()); + + bone = TypicalBone.findWithPrimary("ボーン01"); + assertTrue(bone.isRoot()); + + return; + } + } diff --git a/src/test/java/jp/sourceforge/mikutoga/typical/TypicalMorphTest.java b/src/test/java/jp/sfjp/mikutoga/typical/TypicalMorphTest.java similarity index 77% rename from src/test/java/jp/sourceforge/mikutoga/typical/TypicalMorphTest.java rename to src/test/java/jp/sfjp/mikutoga/typical/TypicalMorphTest.java index 618cfe0..521aef7 100644 --- a/src/test/java/jp/sourceforge/mikutoga/typical/TypicalMorphTest.java +++ b/src/test/java/jp/sfjp/mikutoga/typical/TypicalMorphTest.java @@ -1,7 +1,7 @@ /* */ -package jp.sourceforge.mikutoga.typical; +package jp.sfjp.mikutoga.typical; import java.util.List; import jp.sfjp.mikutoga.pmd.MorphType; @@ -37,7 +37,7 @@ public class TypicalMorphTest { } /** - * Test of getTypedMorphList method, of class TypicalMorph. + * Test of getTypicalMorphList method, of class TypicalMorph. */ @Test public void testGetTypedMorphList() { @@ -45,16 +45,16 @@ public class TypicalMorphTest { List morphList; - morphList = TypicalMorph.getTypedMorphList(MorphType.EYEBROW); + morphList = TypicalMorph.getTypicalMorphList(MorphType.EYEBROW); assertEquals(6, morphList.size()); - morphList = TypicalMorph.getTypedMorphList(MorphType.EYE); + morphList = TypicalMorph.getTypicalMorphList(MorphType.EYE); assertEquals(7, morphList.size()); - morphList = TypicalMorph.getTypedMorphList(MorphType.LIP); + morphList = TypicalMorph.getTypicalMorphList(MorphType.LIP); assertEquals(12, morphList.size()); - morphList = TypicalMorph.getTypedMorphList(MorphType.EXTRA); + morphList = TypicalMorph.getTypicalMorphList(MorphType.EXTRA); assertEquals(2, morphList.size()); return; @@ -74,10 +74,10 @@ public class TypicalMorphTest { assertEquals(MorphType.LIP, result.getMorphType()); assertEquals("あ", result.getTopPrimaryName()); assertEquals("a", result.getTopGlobalName()); - assertEquals(1, result.getPrimaryList().size()); - assertEquals("あ", result.getPrimaryList().get(0)); - assertEquals(1, result.getGlobalList().size()); - assertEquals("a", result.getGlobalList().get(0)); + assertEquals(1, result.getPrimaryNameList().size()); + assertEquals("あ", result.getPrimaryNameList().get(0)); + assertEquals(1, result.getGlobalNameList().size()); + assertEquals("a", result.getGlobalNameList().get(0)); TypicalMorph result1; TypicalMorph result2; @@ -88,11 +88,11 @@ public class TypicalMorphTest { assertEquals(MorphType.EXTRA, result1.getMorphType()); assertEquals("べー", result1.getTopPrimaryName()); assertEquals("tongue", result1.getTopGlobalName()); - assertEquals(2, result1.getPrimaryList().size()); - assertEquals("べー", result1.getPrimaryList().get(0)); - assertEquals("ぺろっ", result1.getPrimaryList().get(1)); - assertEquals(1, result1.getGlobalList().size()); - assertEquals("tongue", result1.getGlobalList().get(0)); + assertEquals(2, result1.getPrimaryNameList().size()); + assertEquals("べー", result1.getPrimaryNameList().get(0)); + assertEquals("ぺろっ", result1.getPrimaryNameList().get(1)); + assertEquals(1, result1.getGlobalNameList().size()); + assertEquals("tongue", result1.getGlobalNameList().get(0)); return; } @@ -176,28 +176,28 @@ public class TypicalMorphTest { List morphList; - morphList = TypicalMorph.getTypedMorphList(MorphType.EYEBROW); + morphList = TypicalMorph.getTypicalMorphList(MorphType.EYEBROW); for(TypicalMorph morph : morphList){ MorphType type = morph.getMorphType(); assertEquals(MorphType.EYEBROW, type); } - morphList = TypicalMorph.getTypedMorphList(MorphType.EYE); + morphList = TypicalMorph.getTypicalMorphList(MorphType.EYE); for(TypicalMorph morph : morphList){ MorphType type = morph.getMorphType(); assertEquals(MorphType.EYE, type); } - morphList = TypicalMorph.getTypedMorphList(MorphType.LIP); + morphList = TypicalMorph.getTypicalMorphList(MorphType.LIP); for(TypicalMorph morph : morphList){ MorphType type = morph.getMorphType(); assertEquals(MorphType.LIP, type); } - morphList = TypicalMorph.getTypedMorphList(MorphType.EXTRA); + morphList = TypicalMorph.getTypicalMorphList(MorphType.EXTRA); for(TypicalMorph morph : morphList){ MorphType type = morph.getMorphType(); diff --git a/src/test/java/jp/sfjp/mikutoga/typical/UniqBoneTest.java b/src/test/java/jp/sfjp/mikutoga/typical/UniqBoneTest.java new file mode 100644 index 0000000..821114f --- /dev/null +++ b/src/test/java/jp/sfjp/mikutoga/typical/UniqBoneTest.java @@ -0,0 +1,61 @@ +/* + */ + +package jp.sfjp.mikutoga.typical; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + */ +public class UniqBoneTest { + + public UniqBoneTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of isPrimaryKneeName method, of class UniqBone. + */ + @Test + public void testIsPrimaryKneeName() { + System.out.println("isPrimaryKneeName"); + + assertTrue(UniqBone.isPrimaryKneeName("左ひざ")); + assertTrue(UniqBone.isPrimaryKneeName("右ひざ")); + assertTrue(UniqBone.isPrimaryKneeName("左ひざ蹴り")); + + assertFalse(UniqBone.isPrimaryKneeName("")); + assertFalse(UniqBone.isPrimaryKneeName("左ひ")); + assertFalse(UniqBone.isPrimaryKneeName("ひざ")); + assertFalse(UniqBone.isPrimaryKneeName("前ひざ")); + assertFalse(UniqBone.isPrimaryKneeName("左ひさ゛")); + assertFalse(UniqBone.isPrimaryKneeName("左ヒザ")); + assertFalse(UniqBone.isPrimaryKneeName("左ヒザ")); + assertFalse(UniqBone.isPrimaryKneeName("左膝")); + assertFalse(UniqBone.isPrimaryKneeName("Knee_L")); + + return; + } + +}