OSDN Git Service

実数表記の揺れを吸収
authorOlyutorskii <olyutorskii@users.osdn.me>
Sun, 27 Jan 2013 22:01:21 +0000 (07:01 +0900)
committerOlyutorskii <olyutorskii@users.osdn.me>
Sun, 27 Jan 2013 22:01:21 +0000 (07:01 +0900)
12 files changed:
CHANGELOG.txt
src/main/java/jp/sourceforge/mikutoga/parser/CommonParser.java
src/main/java/jp/sourceforge/mikutoga/parser/ParseStage.java
src/main/java/jp/sourceforge/mikutoga/pmd/ModelFileType.java [new file with mode: 0644]
src/main/java/jp/sourceforge/mikutoga/typical/I18nAlias.java [moved from src/main/java/jp/sourceforge/mikutoga/typical/TypicalObject.java with 62% similarity]
src/main/java/jp/sourceforge/mikutoga/typical/TypicalBone.java
src/main/java/jp/sourceforge/mikutoga/typical/TypicalMorph.java
src/main/java/jp/sourceforge/mikutoga/xml/BasicXmlExporter.java
src/main/java/jp/sourceforge/mikutoga/xml/DomUtils.java
src/main/java/jp/sourceforge/mikutoga/xml/XmlResourceResolver.java
src/test/java/jp/sourceforge/mikutoga/typical/TypicalBoneTest.java [new file with mode: 0644]
src/test/java/jp/sourceforge/mikutoga/typical/TypicalMorphTest.java [new file with mode: 0644]

index 5cb8762..fc50b2f 100644 (file)
@@ -6,6 +6,7 @@ TogaGem 変更履歴
 
 X.XXX.X (20XX-XX-XX)
     ・Maven3対応。
+    ・JRE版数によって"0.001"や"0.0010"になるXML上の実数表記揺れを吸収。
 
 2.101.2 (2011-08-24)
     ・VMDファイルの読み込みに対応。
index 8ee1a7c..bbbc473 100644 (file)
@@ -183,7 +183,6 @@ public class CommonParser {
      * @return 読み込んだbyte値
      * @throws IOException IOエラー
      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
-     * @see MmdInputStream#parseByte()
      */
     protected byte parseByte()
             throws IOException, MmdEofException{
@@ -204,7 +203,6 @@ public class CommonParser {
      * @return 読み込まれた値のint値
      * @throws IOException IOエラー
      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
-     * @see MmdInputStream#parseUByteAsInt()
      */
     protected int parseUByteAsInt()
             throws IOException, MmdEofException{
@@ -217,7 +215,6 @@ public class CommonParser {
      * @return 読み込まれた値のboolean値
      * @throws IOException IOエラー
      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
-     * @see MmdInputStream#parseBoolean()
      */
     protected boolean parseBoolean()
             throws IOException, MmdEofException{
@@ -232,7 +229,6 @@ public class CommonParser {
      * @return 読み込んだshort値
      * @throws IOException IOエラー
      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
-     * @see MmdInputStream#parseShort()
      */
     protected short parseLeShort()
             throws IOException, MmdEofException{
@@ -248,7 +244,6 @@ public class CommonParser {
      * @return 読み込まれた値のint値
      * @throws IOException IOエラー
      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
-     * @see MmdInputStream#parseUShortAsInteger()
      */
     protected int parseLeUShortAsInt()
             throws IOException, MmdEofException{
@@ -261,7 +256,6 @@ public class CommonParser {
      * @return 読み込んだint値
      * @throws IOException IOエラー
      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
-     * @see MmdInputStream#parseInteger()
      */
     protected int parseLeInt()
             throws IOException, MmdEofException{
@@ -276,7 +270,6 @@ public class CommonParser {
      * @return 読み込んだfloat値
      * @throws IOException IOエラー
      * @throws MmdEofException 読み込む途中でストリーム終端に達した。
-     * @see MmdInputStream#parseFloat()
      */
     protected float parseLeFloat()
             throws IOException, MmdEofException{
index ffa1e14..f43b094 100644 (file)
@@ -31,7 +31,7 @@ public class ParseStage {
 
     /**
      * 各インスタンスに割り当てられたユニークな通し番号を返す。
-     * @return
+     * @return 通し番号
      */
     public int getNo(){
         return this.no;
diff --git a/src/main/java/jp/sourceforge/mikutoga/pmd/ModelFileType.java b/src/main/java/jp/sourceforge/mikutoga/pmd/ModelFileType.java
new file mode 100644 (file)
index 0000000..06cf18c
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * MMD model file types.
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 MikuToga Partners
+ */
+
+package jp.sourceforge.mikutoga.pmd;
+
+/**
+ * モデルファイル種別。
+ */
+public enum ModelFileType {
+
+    /** 不明。 */
+    NONE,
+
+    /** MikuMikuDance ver7 前後で読み書きが可能なPMDファイル。 */
+    PMD,
+
+    /**
+     * スキーマ
+     * http://mikutoga.sourceforge.jp/xml/xsd/pmdxml-101009.xsd
+     * で定義されたXMLファイル。
+     */
+    XML_101009,
+
+    ;
+
+}
@@ -1,5 +1,5 @@
 /*
- * typical object information
+ * internationalization name alias
  *
  * License : The MIT License
  * Copyright(c) 2011 MikuToga Partners
@@ -9,6 +9,7 @@ package jp.sourceforge.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;
@@ -22,34 +23,36 @@ import org.w3c.dom.Element;
 import org.xml.sax.SAXException;
 
 /**
- * å\90\84種æ¨\99æº\96オブジェクトの実装基板。
+ * å\9b½é\9a\9bå\8c\96\88¥å\90\8d管ç\90\86オブジェクトの実装基板。
  * <p>国産モデルではプライマリ名に日本語名が収められることが多い。
  * プライマリ名は必ず一つ以上なければならない。
  * <p>国産モデルではグローバル名に英語名が収められることが多いが、
  * プライマリ名と同一の日本語名が収められている場合も多い。
  */
-class TypicalObject {
+class I18nAlias {
 
     /** オーダ番号によるコンパレータ。 */
-    public static final Comparator<TypicalObject> ORDER_COMPARATOR =
+    public static final Comparator<I18nAlias> ORDER_COMPARATOR =
             new OrderComparator();
 
-    protected final List<String> primaryList;
-    protected final List<String> globalList;
 
-    protected final List<String> umodPrimaryList;
-    protected final List<String> umodGlobalList;
+    private int orderNo;
 
-    protected int orderNo;
+    private final List<String> primaryList;
+    private final List<String> globalList;
+
+    private final List<String> umodPrimaryList;
+    private final List<String> umodGlobalList;
 
 
     /**
      * コンストラクタ。
-     * <p>各初期数が0以下の場合は、状況に応じて伸長する連結リストが用意される。
+     * <p>各初期数が0以下の場合は、
+     * 状況に応じて伸長する連結リストが用意される。
      * @param primaryNo プライマリ名初期数。
      * @param globalNo グローバル名初期数。
      */
-    protected TypicalObject(int primaryNo, int globalNo){
+    protected I18nAlias(int primaryNo, int globalNo){
         super();
 
         if(primaryNo <= 0){
@@ -74,9 +77,10 @@ class TypicalObject {
 
     /**
      * コンストラクタ。
-     * <p>プライマリ名、グローバル名共、状況に応じて伸長する連結リストが用意される。
+     * <p>プライマリ名、グローバル名共、
+     * 状況に応じて伸長する連結リストが用意される。
      */
-    protected TypicalObject(){
+    protected I18nAlias(){
         this(0, 0);
         return;
     }
@@ -104,9 +108,41 @@ class TypicalObject {
         return top;
     }
 
+    /**
+     * NFKC正規化された文字列を返す。
+     * <ul>
+     * <li>「ボーン」は「ボーン」になる
+     * <li>「ホ゛ーン9」は「ボーン9」になる
+     * </ul>
+     * @param name
+     * @return
+     */
+    protected static String normalize(CharSequence name){
+        String result;
+        result = Normalizer.normalize(name, Normalizer.Form.NFKC);
+        return result;
+    }
+
+
+    /**
+     * オーダー番号を返す。
+     * @return
+     */
+    protected int getOrderNo(){
+        return this.orderNo;
+    }
+
+    /**
+     * オーダー番号を設定する。
+     * @param orderNo
+     */
+    protected void setOrderNo(int orderNo){
+        this.orderNo = orderNo;
+        return;
+    }
 
     /**
-     * プライマリ名をひとつ返す。
+     * ã\83\97ã\83©ã\82¤ã\83\9eã\83ªå\90\8dã\81®ä»£è¡¨ã\82\92ã\81²ã\81¨ã\81¤è¿\94ã\81\99ã\80\82
      * @return 最初のプライマリ名
      */
     public String getTopPrimaryName(){
@@ -115,7 +151,7 @@ class TypicalObject {
     }
 
     /**
-     * グローバル名をひとつ返す。
+     * ã\82°ã\83­ã\83¼ã\83\90ã\83«å\90\8dã\81®ä»£è¡¨ã\82\92ã\81²ã\81¨ã\81¤è¿\94ã\81\99ã\80\82
      * @return 最初のグローバル名。ひとつもなければnull
      */
     public String getTopGlobalName(){
@@ -126,7 +162,7 @@ class TypicalObject {
     }
 
     /**
-     * 全プライマリ名を返す。
+     * プライマリ名の全エイリアス文字列リストを返す。
      * @return 全プライマリ名リスト。(不可変)
      */
     public List<String> getPrimaryList(){
@@ -134,7 +170,16 @@ class TypicalObject {
     }
 
     /**
-     * 全グローバル名を返す。
+     * プライマリ名を追加。
+     * @param name プライマリ名
+     */
+    protected void addPrimaryName(String name){
+        this.primaryList.add(name);
+        return;
+    }
+
+    /**
+     * グローバル名の全エイリアス文字列リストを返す。
      * @return 全グローバル名リスト。(不可変)
      */
     public List<String> getGlobalList(){
@@ -142,10 +187,20 @@ class TypicalObject {
     }
 
     /**
+     * グローバル名を追加。
+     * @param name グローバル名
+     */
+    protected void addGlobalName(String name){
+        this.globalList.add(name);
+        return;
+    }
+
+    /**
      * オーダ番号によるコンパレータ。
      */
+    @SuppressWarnings("serial")
     private static class OrderComparator
-            implements Comparator<TypicalObject> {
+            implements Comparator<I18nAlias> {
 
         /**
          * コンストラクタ。
@@ -162,8 +217,8 @@ class TypicalObject {
          * @return {@inheritDoc}
          */
         @Override
-        public int compare(TypicalObject o1, TypicalObject o2){
-            int result = o1.orderNo - o2.orderNo;
+        public int compare(I18nAlias o1, I18nAlias o2){
+            int result = o1.getOrderNo() - o2.getOrderNo();
             return result;
         }
 
index 1be886a..4f907e8 100644 (file)
@@ -9,6 +9,7 @@ package jp.sourceforge.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;
@@ -25,7 +26,7 @@ import org.xml.sax.SAXException;
  * <p>選択基準は独断。
  * <p>和英対訳はMMD Ver7.39の同梱モデルにほぼ準拠。
  */
-public final class TypicalBone extends TypicalObject {
+public final class TypicalBone extends I18nAlias {
 
     private static final Class<?> THISCLASS = TypicalBone.class;
     private static final String BONE_XML = "resources/typicalBone.xml";
@@ -37,12 +38,14 @@ public final class TypicalBone extends TypicalObject {
     private static final Map<String, TypicalBone> GLOBAL_MAP =
             new HashMap<String, TypicalBone>();
 
+    private static final List<TypicalBone> TYP_BONE_UNMODLIST =
+            Collections.unmodifiableList(TYP_BONE_LIST);
 
     static{
         InputStream is = THISCLASS.getResourceAsStream(BONE_XML);
         Element top;
         try{
-            top = TypicalObject.loadXml(is);
+            top = I18nAlias.loadXml(is);
         }catch(ParserConfigurationException e){
             throw new ExceptionInInitializerError(e);
         }catch(SAXException e){
@@ -59,7 +62,8 @@ public final class TypicalBone extends TypicalObject {
 
     /**
      * コンストラクタ。
-     * <p>各初期数が0以下の場合は、状況に応じて伸長する連結リストが用意される。
+     * <p>各初期数が0以下の場合は、
+     * 状況に応じて伸長する連結リストが用意される。
      * @param primaryNo プライマリ名初期数。
      * @param globalNo グローバル名初期数。
      */
@@ -101,21 +105,23 @@ public final class TypicalBone extends TypicalObject {
         for(int idx = 0; idx < primaryNo; idx++){
             Element primary = (Element) primaryNodes.item(idx);
             String name = primary.getAttribute("name");
-            typBone.primaryList.add(name);
+            typBone.addPrimaryName(name);
         }
 
         for(int idx = 0; idx < globalNo; idx++){
             Element global = (Element) globalNodes.item(idx);
             String name = global.getAttribute("name");
-            typBone.globalList.add(name);
+            typBone.addGlobalName(name);
         }
 
-        for(String primaryName : typBone.primaryList){
-            PRIMARY_MAP.put(primaryName, typBone);
+        for(String primaryName : typBone.getPrimaryList()){
+            String key = normalize(primaryName).intern();
+            PRIMARY_MAP.put(key, typBone);
         }
 
-        for(String globalName : typBone.globalList){
-            GLOBAL_MAP.put(globalName, typBone);
+        for(String globalName : typBone.getGlobalList()){
+            String key = normalize(globalName).intern();
+            GLOBAL_MAP.put(key, typBone);
         }
 
         return typBone;
@@ -128,29 +134,41 @@ public final class TypicalBone extends TypicalObject {
     private static void numbering(){
         int order = 0;
         for(TypicalBone bone : TYP_BONE_LIST){
-            bone.orderNo = order++;
+            bone.setOrderNo(order++);
         }
 
         return;
     }
 
     /**
+     * 全ボーンの不変リストを返す。
+     * @return 全ボーンのリスト
+     */
+    public static List<TypicalBone> getBoneList(){
+        return TYP_BONE_UNMODLIST;
+    }
+
+    /**
      * プライマリ名の合致するボーン情報を返す。
+     * NFKCで正規化されたプライマリ名で検索される。
      * @param primaryName プライマリ名
      * @return モーフ情報。見つからなければnull
      */
     public static TypicalBone findWithPrimary(String primaryName){
-        TypicalBone result = PRIMARY_MAP.get(primaryName);
+        String key = normalize(primaryName);
+        TypicalBone result = PRIMARY_MAP.get(key);
         return result;
     }
 
     /**
      * グローバル名の合致するボーン情報を返す。
+     * NFKCで正規化されたグローバル名で検索される。
      * @param globalName グローバル名
      * @return モーフ情報。見つからなければnull
      */
     public static TypicalBone findWithGlobal(String globalName){
-        TypicalBone result = GLOBAL_MAP.get(globalName);
+        String key = normalize(globalName);
+        TypicalBone result = GLOBAL_MAP.get(key);
         return result;
     }
 
index 12c37d8..0c552fe 100644 (file)
@@ -29,7 +29,7 @@ import org.xml.sax.SAXException;
  * <p>選択基準は独断。
  * <p>和英対訳はMMD Ver7.39の同梱モデルにほぼ準拠。
  */
-public final class TypicalMorph extends TypicalObject {
+public final class TypicalMorph extends I18nAlias {
 
     private static final Class<?> THISCLASS = TypicalMorph.class;
     private static final String MORPH_XML = "resources/typicalMorph.xml";
@@ -49,7 +49,7 @@ public final class TypicalMorph extends TypicalObject {
         InputStream is = THISCLASS.getResourceAsStream(MORPH_XML);
         Element top;
         try{
-            top = TypicalObject.loadXml(is);
+            top = I18nAlias.loadXml(is);
         }catch(ParserConfigurationException e){
             throw new ExceptionInInitializerError(e);
         }catch(SAXException e){
@@ -69,7 +69,8 @@ public final class TypicalMorph extends TypicalObject {
 
     /**
      * コンストラクタ。
-     * <p>各初期数が0以下の場合は、状況に応じて伸長する連結リストが用意される。
+     * <p>各初期数が0以下の場合は、
+     * 状況に応じて伸長する連結リストが用意される。
      * @param type モーフ種別
      * @param primaryNo プライマリ名初期数。
      * @param globalNo グローバル名初期数。
@@ -93,7 +94,7 @@ public final class TypicalMorph extends TypicalObject {
             parseGroup(group);
         }
 
-        // 空リスト登録
+        // 必要に応じモーフ枠に不変空リスト登録
         for(MorphType morphType : MorphType.values()){
             if( ! TYPED_MAP.containsKey(morphType) ){
                 TYPED_MAP.put(morphType, EMPTY);
@@ -113,7 +114,8 @@ public final class TypicalMorph extends TypicalObject {
 
         NodeList morphList = group.getElementsByTagName("morph");
         int morphNo = morphList.getLength();
-        List<TypicalMorph> groupedList = new ArrayList<TypicalMorph>(morphNo);
+        List<TypicalMorph> groupedList =
+                new ArrayList<TypicalMorph>(morphNo);
 
         for(int idx = 0; idx < morphNo; idx++){
             Element morph = (Element) morphList.item(idx);
@@ -121,7 +123,8 @@ public final class TypicalMorph extends TypicalObject {
             groupedList.add(common);
         }
 
-        TYPED_MAP.put(morphType, Collections.unmodifiableList(groupedList));
+        groupedList = Collections.unmodifiableList(groupedList);
+        TYPED_MAP.put(morphType, groupedList);
 
         return;
     }
@@ -143,21 +146,23 @@ public final class TypicalMorph extends TypicalObject {
         for(int idx = 0; idx < primaryNo; idx++){
             Element primary = (Element) primaryNodes.item(idx);
             String name = primary.getAttribute("name");
-            typMorph.primaryList.add(name);
+            typMorph.addPrimaryName(name);
         }
 
         for(int idx = 0; idx < globalNo; idx++){
             Element global = (Element) globalNodes.item(idx);
             String name = global.getAttribute("name");
-            typMorph.globalList.add(name);
+            typMorph.addGlobalName(name);
         }
 
-        for(String primaryName : typMorph.primaryList){
-            PRIMARY_MAP.put(primaryName, typMorph);
+        for(String primaryName : typMorph.getPrimaryList()){
+            String key = normalize(primaryName).intern();
+            PRIMARY_MAP.put(key, typMorph);
         }
 
-        for(String globalName : typMorph.globalList){
-            GLOBAL_MAP.put(globalName, typMorph);
+        for(String globalName : typMorph.getGlobalList()){
+            String key = normalize(globalName).intern();
+            GLOBAL_MAP.put(key, typMorph);
         }
 
         return typMorph;
@@ -171,7 +176,7 @@ public final class TypicalMorph extends TypicalObject {
         int order = 0;
         for(MorphType morphType : MorphType.values()){
             for(TypicalMorph common : TYPED_MAP.get(morphType)){
-                common.orderNo = order++;
+                common.setOrderNo(order++);
             }
         }
 
@@ -191,21 +196,25 @@ public final class TypicalMorph extends TypicalObject {
 
     /**
      * プライマリ名の合致するモーフ情報を返す。
+     * NFKCで正規化されたプライマリ名で検索される。
      * @param primaryName プライマリ名
      * @return モーフ情報。見つからなければnull
      */
     public static TypicalMorph findWithPrimary(String primaryName){
-        TypicalMorph result = PRIMARY_MAP.get(primaryName);
+        String key = normalize(primaryName);
+        TypicalMorph result = PRIMARY_MAP.get(key);
         return result;
     }
 
     /**
      * グローバル名の合致するモーフ情報を返す。
+     * NFKCで正規化されたグローバル名で検索される。
      * @param globalName グローバル名
      * @return モーフ情報。見つからなければnull
      */
     public static TypicalMorph findWithGlobal(String globalName){
-        TypicalMorph result = GLOBAL_MAP.get(globalName);
+        String key = normalize(globalName);
+        TypicalMorph result = GLOBAL_MAP.get(key);
         return result;
     }
 
@@ -233,6 +242,7 @@ public final class TypicalMorph extends TypicalObject {
         return primary;
     }
 
+
     /**
      * モーフ種別を返す。
      * @return モーフ種別
index 92cab4c..b429c55 100644 (file)
@@ -14,6 +14,8 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.nio.charset.Charset;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import javax.xml.bind.DatatypeConverter;
 
 /**
@@ -26,34 +28,42 @@ public class BasicXmlExporter {
     private static final Charset CS_UTF8 = Charset.forName("UTF-8");
 
     /** デフォルトの改行文字列。 */
-    private static final String LF = "\n";       // 0x0a
+    private static final String DEF_NL = "\n";       // 0x0a(LF)
     /** デフォルトのインデント単位。 */
-    private static final String DEFAULT_INDENT_UNIT = "\u0020\u0020";
+    private static final String DEF_INDENT_UNIT = "\u0020\u0020"; // ␣␣
 
-    private static final char CH_SP     = '\u0020';       //
-    private static final char CH_YEN    = '\u00a5';       // ¥
-    private static final char CH_BSLASH = '\u005c\u005c'; // \
+    private static final char CH_SP     = '\u0020';    // ␣
+    private static final char CH_YEN    = '\u00a5';    // ¥
+    private static final char CH_BSLASH = (char)0x005c; // \
+    private static final char CH_DQ     = '\u0022';    // "
+    private static final char CH_SQ     = (char)0x0027; // '
 
+    private static final String COMM_START = "<!--";
+    private static final String COMM_END   =   "-->";
+    private static final String REF_HEX = "&#x";
+
+    private static final Pattern NUM_FUZZY =
+            Pattern.compile("([^.]*\\.[0-9][0-9]*?)0+");
+
+    private static final int HEX_EXP = 4;    // 2 ** 4 == 16
+    private static final int MASK_1HEX = (1 << HEX_EXP) - 1;  // 0b00001111
+    private static final int MAX_OCTET = (1 << Byte.SIZE) - 1;   // 0xff
     private static final char[] HEXCHAR_TABLE = {
         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
         'A', 'B', 'C', 'D', 'E', 'F',
     };
 
-    private static final String COMM_START = "<!--";
-    private static final String COMM_END   =     "-->";
-
-    private static final int MASK_BIT8  = 0x000f;
-    private static final int MASK_BIT16 = 0x00ff;
 
     static{
-        assert HEXCHAR_TABLE.length == 16;
+        assert HEX_EXP * 2 == Byte.SIZE;
+        assert HEXCHAR_TABLE.length == (1 << HEX_EXP);
     }
 
 
     private final Appendable appendable;
 
-    private String newline = LF;
-    private String indentUnit = DEFAULT_INDENT_UNIT;
+    private String newline = DEF_NL;
+    private String indentUnit = DEF_INDENT_UNIT;
 
     private int indentNest = 0;
     private boolean basicLatinOnlyOut = true;
@@ -93,33 +103,47 @@ public class BasicXmlExporter {
         return;
     }
 
+
     /**
      * ASCIIコード相当(UCS:Basic-Latin)の文字か否か判定する。
+     * <p>※ Basic-Latinには各種制御文字も含まれる。
      * @param ch 判定対象文字
      * @return Basic-Latin文字ならtrue
      */
     public static boolean isBasicLatin(char ch){
-        Character.UnicodeBlock block = Character.UnicodeBlock.of(ch);
-        if(block == Character.UnicodeBlock.BASIC_LATIN) return true;
+        if('\u0000' <= ch && ch <= '\u007f'){
+            return true;
+        }
         return false;
     }
 
     /**
-     * 改行文字列を設定する。
-     * @param newLine 改行文字列
-     * @throws NullPointerException 引数がnull
+     * 冗長な実数出力を抑止する。
+     * <p>DatatypeConverterにおけるJDK1.6系と1.7系の仕様変更を吸収する。
+     * <p>0.001fは"0.0010"ではなく"0.001"と出力される。
+     * <p>指数表記での冗長桁は無視する。
+     * @param numTxt 実数表記
+     * @return 冗長桁が抑止された実数表記
      */
-    public void setNewLine(String newLine) throws NullPointerException{
-        if(newLine == null) throw new NullPointerException();
-        this.newline = newLine;
-        return;
+    public static String chopFuzzyZero(String numTxt){
+        String result;
+
+        Matcher matcher = NUM_FUZZY.matcher(numTxt);
+        if(matcher.matches()){
+            result = matcher.group(1);
+        }else{
+            result = numTxt;
+        }
+
+        return result;
     }
 
+
     /**
      * BasicLatin文字だけで出力するか設定する。
-     * BasicLatin以外の文字(≒日本語)をそのまま出力するか
-     * 文字参照で出力するかの設定が可能。
-     * コメント部中身は対象外。
+     * <p>BasicLatin以外の文字(≒日本語)を、そのまま出力するか、
+     * æ\96\87å­\97å\8f\82ç\85§ã\81§å\87ºå\8a\9bã\81\99ã\82\8bã\81\8bã\80\81ã\81®è¨­å®\9aã\81\8cå\8f¯è\83½ã\80\82
+     * <p>コメント部中身は対象外。
      * @param bool BasicLatin文字だけで出力するならtrue
      */
     public void setBasicLatinOnlyOut(boolean bool){
@@ -129,7 +153,7 @@ public class BasicXmlExporter {
 
     /**
      * BasicLatin文字だけを出力する状態か判定する。
-     * コメント部中身は対象外。
+     * <p>コメント部中身は対象外。
      * @return BasicLatin文字だけで出力するならtrue
      */
     public boolean isBasicLatinOnlyOut(){
@@ -138,23 +162,41 @@ public class BasicXmlExporter {
 
     /**
      * 改行文字列を設定する。
-     * デフォルトではLF(0x0a)\nが用いられる。
-     * @param seq 改行文字列。nullは空文字列""と解釈される。
+     * @param newLine 改行文字列
+     * @throws NullPointerException 引数がnull
      */
-    public void setNewLine(CharSequence seq){
-        if(seq == null) this.newline = "";
-        else            this.newline = seq.toString();
+    public void setNewLine(String newLine) throws NullPointerException{
+        if(newLine == null) throw new NullPointerException();
+        this.newline = newLine;
         return;
     }
 
     /**
+     * 改行文字列を返す。
+     * @return 改行文字列
+     */
+    public String getNewLine(){
+        return this.newline;
+    }
+
+    /**
      * インデント単位文字列を設定する。
-     * デフォルトでは空白2個。
-     * @param seq インデント単位文字列。nullは空文字列""と解釈される。
+     * <p>デフォルトでは空白2個。
+     * @param indUnit インデント単位文字列。
+     * @throws NullPointerException 引数がnull
+     */
+    public void setIndentUnit(String indUnit) throws NullPointerException{
+        if(indUnit == null) throw new NullPointerException();
+        this.indentUnit = indUnit;
+        return;
+    }
+
+    /**
+     * インデント単位文字列を返す。
+     * @return インデント単位文字列
      */
-    public void setIndentUnit(CharSequence seq){
-        if(seq == null) this.indentUnit = "";
-        else            this.indentUnit = seq.toString();
+    public String getIndentUnit(){
+        return this.indentUnit;
     }
 
     /**
@@ -185,7 +227,7 @@ public class BasicXmlExporter {
      * @return this本体
      * @throws IOException 出力エラー
      */
-    public BasicXmlExporter put(char ch) throws IOException{
+    public BasicXmlExporter putRawCh(char ch) throws IOException{
         this.appendable.append(ch);
         return this;
     }
@@ -196,34 +238,134 @@ public class BasicXmlExporter {
      * @return this本体
      * @throws IOException 出力エラー
      */
-    public BasicXmlExporter put(CharSequence seq) throws IOException{
+    public BasicXmlExporter putRawText(CharSequence seq) throws IOException{
         this.appendable.append(seq);
         return this;
     }
 
     /**
-     * int値を出力する。
-     * @param iVal int値
+     * 指定された文字を16進2桁の文字参照形式で出力する。
+     * 2桁で出力できない場合(>0x00ff)は4桁で出力する。
+     * @param ch 文字
      * @return this本体
      * @throws IOException 出力エラー
-     * @see java.lang.Integer#toString(int)
      */
-    public BasicXmlExporter put(int iVal) throws IOException{
-        String value = DatatypeConverter.printInt(iVal);
-        this.appendable.append(value);
+    public BasicXmlExporter putCharRef2Hex(char ch) throws IOException{
+        if(ch > MAX_OCTET) return putCharRef4Hex(ch);
+
+        int ibits = ch;   // 常に正なので符号拡張なし
+
+        int idx4 = ibits & MASK_1HEX;
+        ibits >>= HEX_EXP;
+        int idx3 = ibits & MASK_1HEX;
+
+        char hex3 = HEXCHAR_TABLE[idx3];
+        char hex4 = HEXCHAR_TABLE[idx4];
+
+        putRawText(REF_HEX).putRawCh(hex3).putRawCh(hex4)
+                           .putRawCh(';');
+
         return this;
     }
 
     /**
-     * float値を出力する。
-     * @param fVal float値
+     * 指定された文字を16進4桁の文字参照形式で出力する。
+     * UCS4に伴うサロゲートペアは未サポート
+     * @param ch 文字
      * @return this本体
      * @throws IOException 出力エラー
-     * @see java.lang.Float#toString(float)
      */
-    public BasicXmlExporter put(float fVal) throws IOException{
-        String value = DatatypeConverter.printFloat(fVal);
-        this.appendable.append(value);
+    public BasicXmlExporter putCharRef4Hex(char ch) throws IOException{
+        int ibits = ch;   // 常に正なので符号拡張なし
+
+        int idx4 = ibits & MASK_1HEX;
+        ibits >>= HEX_EXP;
+        int idx3 = ibits & MASK_1HEX;
+        ibits >>= HEX_EXP;
+        int idx2 = ibits & MASK_1HEX;
+        ibits >>= HEX_EXP;
+        int idx1 = ibits & MASK_1HEX;
+
+        char hex1 = HEXCHAR_TABLE[idx1];
+        char hex2 = HEXCHAR_TABLE[idx2];
+        char hex3 = HEXCHAR_TABLE[idx3];
+        char hex4 = HEXCHAR_TABLE[idx4];
+
+        putRawText(REF_HEX).putRawCh(hex1).putRawCh(hex2)
+                           .putRawCh(hex3).putRawCh(hex4)
+                           .putRawCh(';');
+
+        return this;
+    }
+
+    /**
+     * 要素の中身および属性値中身を出力する。
+     * <p>XMLの構文規則を守る上で必要な各種エスケープ処理が行われる。
+     * @param ch 文字
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    public BasicXmlExporter putCh(char ch) throws IOException{
+        if(Character.isISOControl(ch)){
+            putCharRef2Hex(ch);
+            return this;
+        }
+
+        String escTxt;
+        switch(ch){
+        case '&':   escTxt = "&amp;";  break;
+        case '<':   escTxt = "&lt;";   break;
+        case '>':   escTxt = "&gt;";   break;
+        case CH_DQ: escTxt = "&quot;"; break;
+        case CH_SQ: escTxt = "&apos;"; break;
+        default:    escTxt = null;     break;
+        }
+
+        if(escTxt != null){
+            putRawText(escTxt);
+        }else{
+            putRawCh(ch);
+        }
+
+        return this;
+    }
+
+    /**
+     * 要素の中身および属性値中身を出力する。
+     * <p>必要に応じてXML定義済み実体文字が割り振られた文字、
+     * コントロールコード、および非BasicLatin文字がエスケープされる。
+     * <p>半角通貨記号U+00A5はバックスラッシュU+005Cに置換される。
+     * <p>連続するスペースU+0020の2文字目以降は文字参照化される。
+     * <p>全角スペースその他空白文字は無条件に文字参照化される。
+     * @param content 内容
+     * @return this本体
+     * @throws IOException 出力エラー
+     */
+    public BasicXmlExporter putContent(CharSequence content)
+            throws IOException{
+        int length = content.length();
+
+        char prev = '\0';
+        for(int pos = 0; pos < length; pos++){
+            char ch = content.charAt(pos);
+
+            if( isBasicLatinOnlyOut() && ! isBasicLatin(ch) ){
+                putCharRef4Hex(ch);
+            }else if(ch == CH_YEN){
+                putRawCh(CH_BSLASH);
+            }else if(Character.isSpaceChar(ch)){
+                if(ch == CH_SP && prev != CH_SP){
+                    putRawCh(ch);
+                }else{
+                    putCharRef2Hex(ch);
+                }
+            }else{
+                putCh(ch);
+            }
+
+            prev = ch;
+        }
+
         return this;
     }
 
@@ -281,7 +423,7 @@ public class BasicXmlExporter {
      */
     public BasicXmlExporter ind() throws IOException{
         for(int ct = 1; ct <= this.indentNest; ct++){
-            put(this.indentUnit);
+            putRawText(this.indentUnit);
         }
         return this;
     }
@@ -305,87 +447,28 @@ public class BasicXmlExporter {
     }
 
     /**
-     * 指定された文字を16進2桁の文字参照形式で出力する。
-     * 2桁で出力できない場合は4桁で出力する。
-     * @param ch 文字
-     * @return this本体
-     * @throws IOException 出力エラー
-     */
-    public BasicXmlExporter putCharRef2Hex(char ch) throws IOException{
-        if(ch > MASK_BIT16) return putCharRef4Hex(ch);
-
-        char hex3 = HEXCHAR_TABLE[(ch >> 4) & MASK_BIT8];
-        char hex4 = HEXCHAR_TABLE[(ch >> 0) & MASK_BIT8];
-
-        put("&#x").put(hex3).put(hex4).put(';');
-
-        return this;
-    }
-
-    /**
-     * 指定された文字を16進4桁の文字参照形式で出力する。
-     * UCS4に伴うサロゲートペアは未サポート
-     * @param ch 文字
+     * int値をXMLスキーマ準拠の形式で出力する。
+     * @param iVal int値
      * @return this本体
      * @throws IOException 出力エラー
+     * @see java.lang.Integer#toString(int)
      */
-    public BasicXmlExporter putCharRef4Hex(char ch) throws IOException{
-        char hex1 = HEXCHAR_TABLE[(ch >> 12) & MASK_BIT8];
-        char hex2 = HEXCHAR_TABLE[(ch >>  8) & MASK_BIT8];
-        char hex3 = HEXCHAR_TABLE[(ch >>  4) & MASK_BIT8];
-        char hex4 = HEXCHAR_TABLE[(ch >>  0) & MASK_BIT8];
-
-        put("&#x").put(hex1).put(hex2).put(hex3).put(hex4).put(';');
-
+    public BasicXmlExporter putXsdInt(int iVal) throws IOException{
+        String value = DatatypeConverter.printInt(iVal);
+        this.appendable.append(value);
         return this;
     }
 
     /**
-     * 要素の中身および属性値中身を出力する。
-     * <p>必要に応じてXML定義済み実体文字が割り振られた文字、
-     * コントロールコード、および非BasicLatin文字がエスケープされる。
-     * <p>半角通貨記号U+00A5はバックスラッシュU+005Cに置換される。
-     * @param content 内容
+     * float値をXMLスキーマ準拠の形式で出力する。
+     * @param fVal float値
      * @return this本体
      * @throws IOException 出力エラー
+     * @see java.lang.Float#toString(float)
      */
-    public BasicXmlExporter putContent(CharSequence content)
-            throws IOException{
-        int length = content.length();
-
-        char prev = '\0';
-        for(int pos = 0; pos < length; pos++){
-            char ch = content.charAt(pos);
-
-            if(Character.isISOControl(ch)){
-                putCharRef2Hex(ch);
-            }else if( ! isBasicLatin(ch) && isBasicLatinOnlyOut()){
-                putCharRef4Hex(ch);
-            }else if(ch == CH_SP){
-                if(prev == CH_SP){
-                    putCharRef2Hex(ch);
-                }else{
-                    put(ch);
-                }
-            }else if(Character.isSpaceChar(ch)){
-                // 全角スペースその他
-                putCharRef2Hex(ch);
-            }else if(ch == CH_YEN){
-                put(CH_BSLASH);
-            }else{
-                switch(ch){
-                case '&':    put("&amp;");    break;
-                case '<':    put("&lt;");     break;
-                case '>':    put("&gt;");     break;
-                case '"':    put("&quot;");   break;
-                case '\'':   put("&apos;");   break;
-                default:     put(ch);         break;
-                }
-            }
-
-            prev = ch;
-        }
-
+    public BasicXmlExporter putXsdFloat(float fVal) throws IOException{
+        String value = DatatypeConverter.printFloat(fVal);
+        this.appendable.append(value);
         return this;
     }
 
@@ -399,7 +482,12 @@ public class BasicXmlExporter {
     public BasicXmlExporter putAttr(CharSequence attrName,
                                      CharSequence content)
             throws IOException{
-        put(attrName).put('=').put('"').putContent(content).put('"');
+        putRawText(attrName).putRawCh('=');
+
+        putRawCh('"');
+        putContent(content);
+        putRawCh('"');
+
         return this;
     }
 
@@ -411,9 +499,10 @@ public class BasicXmlExporter {
      * @throws IOException 出力エラー
      */
     public BasicXmlExporter putIntAttr(CharSequence attrName,
-                                           int iVal)
+                                        int iVal)
             throws IOException{
-        put(attrName).put('=').put('"').put(iVal).put('"');
+        String attrValue = DatatypeConverter.printInt(iVal);
+        putAttr(attrName, attrValue);
         return this;
     }
 
@@ -425,9 +514,11 @@ public class BasicXmlExporter {
      * @throws IOException 出力エラー
      */
     public BasicXmlExporter putFloatAttr(CharSequence attrName,
-                                              float fVal)
+                                           float fVal)
             throws IOException{
-        put(attrName).put('=').put('"').put(fVal).put('"');
+        String attrValue = DatatypeConverter.printFloat(fVal);
+        attrValue = chopFuzzyZero(attrValue);
+        putAttr(attrName, attrValue);
         return this;
     }
 
@@ -454,13 +545,13 @@ public class BasicXmlExporter {
             if(ch == '\n'){
                 ln();
             }else if('\u0000' <= ch && ch <= '\u001f'){
-                put((char)('\u2400' + ch));
+                putRawCh((char)('\u2400' + ch));
             }else if(ch == '\u007f'){
-                put('\u2421');
+                putRawCh('\u2421');
             }else if(prev == '-' && ch == '-'){
-                sp().put(ch);
+                sp().putRawCh(ch);
             }else{
-                put(ch);
+                putRawCh(ch);
             }
 
             prev = ch;
@@ -478,9 +569,9 @@ public class BasicXmlExporter {
      */
     public BasicXmlExporter putLineComment(CharSequence comment)
             throws IOException{
-        put(COMM_START).sp();
+        putRawText(COMM_START).sp();
         putCommentContent(comment);
-        sp().put(COMM_END);
+        sp().putRawText(COMM_END);
         return this;
     }
 
@@ -496,7 +587,7 @@ public class BasicXmlExporter {
      */
     public BasicXmlExporter putBlockComment(CharSequence comment)
             throws IOException{
-        put(COMM_START).ln();
+        putRawText(COMM_START).ln();
 
         putCommentContent(comment);
 
@@ -508,7 +599,7 @@ public class BasicXmlExporter {
             }
         }
 
-        put(COMM_END).ln();
+        putRawText(COMM_END).ln();
 
         return this;
     }
index 66dea2d..c516563 100644 (file)
@@ -314,7 +314,7 @@ public final class DomUtils {
     /**
      * 同じ親要素と同じ要素名を持つ兄弟要素を列挙する列挙子。
      */
-    private static final class ElemIterator implements Iterator<Element>{
+    private static final class ElemIterator implements Iterator<Element> {
         private Element next;
 
         /**
index dc7ffd0..bed0634 100644 (file)
@@ -13,6 +13,7 @@ import java.io.Reader;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import org.w3c.dom.ls.LSInput;
@@ -39,10 +40,14 @@ public class XmlResourceResolver
 
     private static final String LOCAL_SCHEMA_XML =
             "resources/xml-2009-01.xsd";
+
     private static final URI EMPTY_URI = URI.create("");
+
     private static final Class<?> THISCLASS = XmlResourceResolver.class;
 
-    private final Map<URI, URI> uriMap = new HashMap<URI, URI>();
+
+    private final Map<URI, URI> uriMap;
+
 
     /**
      * コンストラクタ。
@@ -52,21 +57,23 @@ public class XmlResourceResolver
 
         assert this.getClass().equals(THISCLASS);
 
+        Map<URI, URI> map;
+        map = new HashMap<URI, URI>();
+        map = Collections.synchronizedMap(map);
+        this.uriMap = map;
+
+        URL redirectRes = THISCLASS.getResource(LOCAL_SCHEMA_XML);
+        String redirectResName = redirectRes.toString();
+
         URI originalURI = URI.create(SCHEMA_XML);
-        URL redirectURL = THISCLASS.getResource(LOCAL_SCHEMA_XML);
-        URI redirectURI;
-        try{
-            redirectURI = redirectURL.toURI();
-        }catch(URISyntaxException e){
-            assert false;
-            throw new AssertionError(e);
-        }
+        URI redirectURI = URI.create(redirectResName);
 
-        this.uriMap.put(originalURI, redirectURI);
+        putURIMapImpl(originalURI, redirectURI);
 
         return;
     }
 
+
     /**
      * 絶対URIと相対URIを合成したURIを返す。
      * 正規化も行われる。
@@ -79,15 +86,21 @@ public class XmlResourceResolver
     protected static URI buildBaseRelativeURI(String base, String relative)
             throws URISyntaxException,
                    IllegalArgumentException {
-        URI baseURI = null;
+        URI baseURI;
         if(base != null){
             baseURI = new URI(base);
-            if( ! baseURI.isAbsolute() ) throw new IllegalArgumentException();
+            if( ! baseURI.isAbsolute() ){
+                throw new IllegalArgumentException();
+            }
+        }else{
+            baseURI = null;
         }
 
-        URI relativeURI = EMPTY_URI;
+        URI relativeURI;
         if(relative != null){
             relativeURI = new URI(relative);
+        }else{
+            relativeURI = EMPTY_URI;
         }
 
         URI resultURI;
@@ -97,7 +110,9 @@ public class XmlResourceResolver
             resultURI = baseURI.resolve(relativeURI);
         }
 
-        if( ! resultURI.isAbsolute() ) throw new IllegalArgumentException();
+        if( ! resultURI.isAbsolute() ){
+            throw new IllegalArgumentException();
+        }
 
         resultURI = resultURI.normalize();
 
@@ -113,6 +128,22 @@ public class XmlResourceResolver
         return input;
     }
 
+
+    /**
+     * オリジナルURIとリダイレクト先のURIを登録する。
+     * オリジナルURIへのアクセスはリダイレクトされる。
+     * @param original オリジナルURI
+     * @param redirect リダイレクトURI
+     */
+    private void putURIMapImpl(URI original, URI redirect){
+        URI oridinalNorm = original.normalize();
+        URI redirectNorm = redirect.normalize();
+
+        this.uriMap.put(oridinalNorm, redirectNorm);
+
+        return;
+    }
+
     /**
      * オリジナルURIとリダイレクト先のURIを登録する。
      * オリジナルURIへのアクセスはリダイレクトされる。
@@ -120,19 +151,25 @@ public class XmlResourceResolver
      * @param redirect リダイレクトURI
      */
     public void putURIMap(URI original, URI redirect){
-        this.uriMap.put(original.normalize(), redirect.normalize());
+        putURIMapImpl(original, redirect);
         return;
     }
 
     /**
-     * 変換後のリソースの入力ストリームを得る。
+     * 登録済みリダイレクト先リソースの入力ストリームを得る。
      * @param originalURI オリジナルURI
-     * @return 入力ストリーム
-     * @throws java.io.IOException 入出力エラー
+     * @return 入力ストリーム。リダイレクト先が未登録の場合はnull
+     * @throws java.io.IOException 入出力エラー。
+     * もしくはリソースが見つからない。
      */
-    public InputStream getXMLResourceAsStream(URI originalURI)
+    private InputStream getXMLResourceAsStream(URI originalURI)
             throws IOException{
-        URI resourceURI = this.uriMap.get(originalURI.normalize());
+        URI keyURI = originalURI.normalize();
+        URI resourceURI = this.uriMap.get(keyURI);
+        if(resourceURI == null){
+            return null;
+        }
+
         URL resourceURL = resourceURI.toURL();
         InputStream is = resourceURL.openStream();
 
@@ -170,6 +207,7 @@ public class XmlResourceResolver
         }catch(IOException e){
             return null;
         }
+        if(is == null) return null;
 
         LSInput input = createLSInput();
         input.setBaseURI(baseURI);
@@ -202,6 +240,7 @@ public class XmlResourceResolver
         }
 
         InputStream is = getXMLResourceAsStream(originalUri);
+        if(is == null) return null;
 
         InputSource source = new InputSource(is);
         source.setPublicId(publicId);
@@ -387,5 +426,4 @@ public class XmlResourceResolver
 
     }
 
-    // TODO OASIS XML Catalog などと調和したい。
 }
diff --git a/src/test/java/jp/sourceforge/mikutoga/typical/TypicalBoneTest.java b/src/test/java/jp/sourceforge/mikutoga/typical/TypicalBoneTest.java
new file mode 100644 (file)
index 0000000..e27d84f
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ */
+
+package jp.sourceforge.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 TypicalBoneTest {
+
+    public TypicalBoneTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() {
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of findWithPrimary method, of class TypicalBone.
+     */
+    @Test
+    public void testFindWithPrimary() {
+        System.out.println("findWithPrimary");
+
+        TypicalBone result;
+        result = TypicalBone.findWithPrimary("頭");
+
+        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));
+
+        return;
+    }
+
+    /**
+     * Test of findWithGlobal method, of class TypicalBone.
+     */
+    @Test
+    public void testFindWithGlobal() {
+        System.out.println("findWithGlobal");
+
+        TypicalBone result;
+
+        result = TypicalBone.findWithGlobal("head");
+        assertNotNull(result);
+
+        TypicalBone result2;
+        result2 = TypicalBone.findWithPrimary("頭");
+
+        assertSame(result, result2);
+
+        return;
+    }
+
+    /**
+     * Test of primary2global method, of class TypicalBone.
+     */
+    @Test
+    public void testPrimary2global() {
+        System.out.println("primary2global");
+
+        String result;
+
+        result = TypicalBone.primary2global("頭");
+        assertEquals("head", result);
+
+        String result1;
+        String result2;
+
+        result1 = TypicalBone.primary2global("ボーン15");
+        result2 = TypicalBone.primary2global("ボーン15");
+        assertSame(result1, result2);
+
+        result = TypicalBone.primary2global("XXX");
+        assertNull(result);
+
+        return;
+    }
+
+    /**
+     * Test of global2primary method, of class TypicalBone.
+     */
+    @Test
+    public void testGlobal2primary() {
+        System.out.println("global2primary");
+
+        String result;
+
+        result = TypicalBone.global2primary("head");
+        assertEquals("頭", result);
+
+        result = TypicalBone.global2primary("head");
+        assertEquals("頭", result);
+
+        result = TypicalBone.global2primary("XXX");
+        assertNull(result);
+
+        return;
+    }
+
+    /**
+     * Test of getBoneList method, of class TypicalBone.
+     */
+    @Test
+    public void testGetBoneList() {
+        System.out.println("getBoneList");
+
+        List<TypicalBone> boneList;
+
+        boneList = TypicalBone.getBoneList();
+
+        assertNotNull(boneList);
+        assertEquals(77, boneList.size());
+
+        TypicalBone bone1st = boneList.get(0);
+        TypicalBone boneLast = boneList.get(77-1);
+
+        assertEquals("センター", bone1st.getTopPrimaryName());
+        assertEquals("ボーン15", boneLast.getTopPrimaryName());
+
+        assertEquals("center", bone1st.getTopGlobalName());
+        assertEquals("bone15", boneLast.getTopGlobalName());
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sourceforge/mikutoga/typical/TypicalMorphTest.java b/src/test/java/jp/sourceforge/mikutoga/typical/TypicalMorphTest.java
new file mode 100644 (file)
index 0000000..8a4bb86
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ */
+
+package jp.sourceforge.mikutoga.typical;
+
+import java.util.List;
+import jp.sourceforge.mikutoga.pmd.MorphType;
+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 TypicalMorphTest {
+
+    public TypicalMorphTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() {
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of getTypedMorphList method, of class TypicalMorph.
+     */
+    @Test
+    public void testGetTypedMorphList() {
+        System.out.println("getTypedMorphList");
+
+        List<TypicalMorph> morphList;
+
+        morphList = TypicalMorph.getTypedMorphList(MorphType.EYEBROW);
+        assertEquals(6, morphList.size());
+
+        morphList = TypicalMorph.getTypedMorphList(MorphType.EYE);
+        assertEquals(7, morphList.size());
+
+        morphList = TypicalMorph.getTypedMorphList(MorphType.LIP);
+        assertEquals(12, morphList.size());
+
+        morphList = TypicalMorph.getTypedMorphList(MorphType.EXTRA);
+        assertEquals(2, morphList.size());
+
+        return;
+    }
+
+    /**
+     * Test of findWithPrimary method, of class TypicalMorph.
+     */
+    @Test
+    public void testFindWithPrimary() {
+        System.out.println("findWithPrimary");
+
+        TypicalMorph result;
+
+        result = TypicalMorph.findWithPrimary("あ");
+        assertNotNull(result);
+        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));
+
+        TypicalMorph result1;
+        TypicalMorph result2;
+
+        result1 = TypicalMorph.findWithPrimary("べー");
+        result2 = TypicalMorph.findWithPrimary("ぺろっ");
+        assertSame(result1, result2);
+        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));
+
+        return;
+    }
+
+    /**
+     * Test of findWithGlobal method, of class TypicalMorph.
+     */
+    @Test
+    public void testFindWithGlobal() {
+        System.out.println("findWithGlobal");
+
+        TypicalMorph result;
+
+        result = TypicalMorph.findWithGlobal("a");
+        assertNotNull(result);
+
+        TypicalMorph result2;
+        result2 = TypicalMorph.findWithPrimary("あ");
+
+        assertSame(result, result2);
+
+        return;
+    }
+
+    /**
+     * Test of primary2global method, of class TypicalMorph.
+     */
+    @Test
+    public void testPrimary2global() {
+        System.out.println("primary2global");
+
+        String result;
+
+        result = TypicalMorph.primary2global("あ");
+        assertEquals("a", result);
+
+        String result1;
+        String result2;
+
+        result1 = TypicalMorph.primary2global("べー");
+        result2 = TypicalMorph.primary2global("ぺろっ");
+        assertSame(result1, result2);
+
+        result1 = TypicalMorph.primary2global("ウィンク");
+        result2 = TypicalMorph.primary2global("ウィンク");
+        assertSame(result1, result2);
+
+        result = TypicalMorph.primary2global("XXX");
+        assertNull(result);
+
+        return;
+    }
+
+    /**
+     * Test of global2primary method, of class TypicalMorph.
+     */
+    @Test
+    public void testGlobal2primary() {
+        System.out.println("global2primary");
+
+        String result;
+
+        result = TypicalMorph.global2primary("a");
+        assertEquals("あ", result);
+
+        result = TypicalMorph.global2primary("tongue");
+        assertEquals("べー", result);
+
+        result = TypicalMorph.global2primary("XXX");
+        assertNull(result);
+
+        return;
+    }
+
+    /**
+     * Test of getMorphType method, of class TypicalMorph.
+     */
+    @Test
+    public void testGetMorphType() {
+        System.out.println("getMorphType");
+
+        List<TypicalMorph> morphList;
+
+        morphList = TypicalMorph.getTypedMorphList(MorphType.EYEBROW);
+
+        for(TypicalMorph morph : morphList){
+            MorphType type = morph.getMorphType();
+            assertEquals(MorphType.EYEBROW, type);
+        }
+
+        morphList = TypicalMorph.getTypedMorphList(MorphType.EYE);
+
+        for(TypicalMorph morph : morphList){
+            MorphType type = morph.getMorphType();
+            assertEquals(MorphType.EYE, type);
+        }
+
+        morphList = TypicalMorph.getTypedMorphList(MorphType.LIP);
+
+        for(TypicalMorph morph : morphList){
+            MorphType type = morph.getMorphType();
+            assertEquals(MorphType.LIP, type);
+        }
+
+        morphList = TypicalMorph.getTypedMorphList(MorphType.EXTRA);
+
+        for(TypicalMorph morph : morphList){
+            MorphType type = morph.getMorphType();
+            assertEquals(MorphType.EXTRA, type);
+        }
+
+        return;
+    }
+
+}