OSDN Git Service

optimize tree model.
authorOlyutorskii <olyutorskii@users.osdn.me>
Fri, 24 Apr 2020 13:33:49 +0000 (22:33 +0900)
committerOlyutorskii <olyutorskii@users.osdn.me>
Fri, 24 Apr 2020 13:33:49 +0000 (22:33 +0900)
src/main/java/jp/sfjp/jindolf/data/Land.java
src/main/java/jp/sfjp/jindolf/data/LandsTreeModel.java
src/main/java/jp/sfjp/jindolf/view/LandsTree.java

index e74b936..cb0b1c0 100644 (file)
@@ -12,8 +12,8 @@ import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.Collections;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -22,6 +22,8 @@ import jp.sourceforge.jindolf.corelib.LandDef;
 
 /**
  * いわゆる「国」。
+ *
+ * 人狼BBSのサーバと1:1の概念。
  */
 public class Land {
 
@@ -31,11 +33,12 @@ public class Land {
     private final LandDef landDef;
     private final ServerAccess serverAccess;
 
-    private final List<Village> villageList = new LinkedList<>();
+    private final List<Village> villageList = new ArrayList<>(1000);
 
 
     /**
      * コンストラクタ。
+     *
      * @param landDef 国定義
      * @throws java.lang.IllegalArgumentException 不正な国定義
      */
@@ -58,6 +61,7 @@ public class Land {
 
     /**
      * 国定義を得る。
+     *
      * @return 国定義
      */
     public LandDef getLandDef(){
@@ -66,6 +70,7 @@ public class Land {
 
     /**
      * サーバ接続を返す。
+     *
      * @return ServerAccessインスタンス
      */
     public ServerAccess getServerAccess(){
@@ -74,6 +79,7 @@ public class Land {
 
     /**
      * 指定されたインデックス位置の村を返す。
+     *
      * @param index 0から始まるインデックス値
      * @return 村
      */
@@ -87,6 +93,7 @@ public class Land {
 
     /**
      * 村の総数を返す。
+     *
      * @return 村の総数
      */
     public int getVillageCount(){
@@ -96,6 +103,7 @@ public class Land {
 
     /**
      * 村のリストを返す。
+     *
      * @return 村のリスト
      */
     // TODO インスタンス変数でいいはず。
@@ -105,7 +113,9 @@ public class Land {
 
     /**
      * 絶対または相対URLの指すパーマネントなイメージ画像をダウンロードする。
-     * ※ A,B,D 国の顔アイコンは絶対パスらしい…。
+     *
+     * <p>※ A,B,D 国の顔アイコンは絶対パスらしい…。
+     *
      * @param imageURL 画像URL文字列
      * @return 画像イメージ
      */
@@ -126,6 +136,7 @@ public class Land {
 
     /**
      * 墓アイコンイメージを取得する。
+     *
      * @return 墓アイコンイメージ
      */
     public BufferedImage getGraveIconImage(){
@@ -136,6 +147,7 @@ public class Land {
 
     /**
      * 墓アイコンイメージ(大)を取得する。
+     *
      * @return 墓アイコンイメージ(大)
      */
     public BufferedImage getGraveBodyImage(){
@@ -146,6 +158,7 @@ public class Land {
 
     /**
      * 村リストを更新する。
+     *
      * @param vset ソート済みの村一覧
      */
     public void updateVillageList(List<Village> vset){
@@ -157,6 +170,7 @@ public class Land {
 
     /**
      * 国の文字列表現を返す。
+     *
      * @return 文字列表現
      */
     @Override
index 0674175..e3fd083 100644 (file)
@@ -7,12 +7,12 @@
 
 package jp.sfjp.jindolf.data;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.logging.Logger;
 import javax.swing.event.EventListenerList;
 import javax.swing.event.TreeModelEvent;
 import javax.swing.event.TreeModelListener;
@@ -21,13 +21,16 @@ import javax.swing.tree.TreePath;
 import jp.sourceforge.jindolf.corelib.LandDef;
 
 /**
- * 国の集合。あらゆるデータモデルの大元。
- * 国一覧と村一覧を管理。
- * JTreeのモデルも兼用。
+ * 国の集合。プレイ対象のあらゆるデータモデルの大元。
+ *
+ * <p>国一覧と村一覧を管理。
+ *
+ * <p>JTreeのモデルも兼用。
+ * ツリー階層は ROOT - 国 - 範囲セクション - 村 の4階層。
  */
-public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付けるか?
+public class LandsTreeModel implements TreeModel{
 
-    private static final String ROOT = "ROOT";
+    private static final Object ROOT = "ROOT";
     private static final int SECTION_INTERVAL = 100;
 
 
@@ -42,6 +45,7 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
 
     private boolean ascending = false;
 
+
     /**
      * コンストラクタ。
      * この時点ではまだ国一覧が読み込まれない。
@@ -51,45 +55,98 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
         return;
     }
 
+
+    /**
+     * ツリーのルートオブジェクトか否か判定する。
+     *
+     * @param obj オブジェクト
+     * @return ルートならtrue
+     */
+    private static boolean isRoot(Object obj){
+        boolean result = obj == ROOT;
+        return result;
+    }
+
     /**
-     * 指定した国の村一覧を更新しイベントを投げる。
+     * 与えられた国の全ての村を指定されたinterval間隔で格納するために、
+     * セクションのリストを生成する。
+     *
      * @param land 国
+     * @param interval セクション間の村ID間隔
+     * @return セクションのリスト
+     * @throws java.lang.IllegalArgumentException intervalが正でない
      */
-    public void updateVillageList(Land land){
-        List<VillageSection> sectionList =
-                VillageSection.getSectionList(land, SECTION_INTERVAL);
-        this.sectionMap.put(land, sectionList);
+    private static List<VillageSection> getSectionList(Land land,
+                                                       int interval )
+            throws IllegalArgumentException{
+        if(interval <= 0){
+            throw new IllegalArgumentException();
+        }
 
-        int[] childIndices = new int[sectionList.size()];
-        for(int ct = 0; ct < childIndices.length; ct++){
-            childIndices[ct] = ct;
+        String pfx = land.getLandDef().getLandPrefix();
+        List<Village> span = new ArrayList<>(interval);
+
+        List<VillageSection> result = new ArrayList<>(2500 / interval);
+
+        int rangeStart = 0;
+        int rangeEnd = 0;
+
+        for(Village village : land.getVillageList()){
+            int vid = village.getVillageIDNum();
+
+            if(rangeStart == rangeEnd){
+                rangeStart = vid / interval * interval;
+                rangeEnd = rangeStart + interval - 1;
+            }
+
+            if(rangeEnd < vid){
+                VillageSection section = new VillageSection(
+                        pfx, rangeStart, rangeEnd, span);
+                result.add(section);
+                span.clear();
+
+                rangeStart = vid / interval * interval;
+                rangeEnd   = rangeStart + interval - 1;
+            }
+
+            span.add(village);
         }
-        Object[] children = sectionList.toArray();
 
-        Object[] path = {ROOT, land};
-        TreePath treePath = new TreePath(path);
-        TreeModelEvent event = new TreeModelEvent(this,
-                                                  treePath,
-                                                  childIndices,
-                                                  children     );
-        fireTreeStructureChanged(event);
+        if( ! span.isEmpty()){
+            VillageSection section = new VillageSection(
+                    pfx, rangeStart, rangeEnd, span);
+            result.add(section);
+        }
 
-        return;
+        return Collections.unmodifiableList(result);
     }
 
+
     /**
-     * 国一覧を読み込む。
+     * 国リストを得る。
+     *
+     * @return 国のリスト
+     */
+    public List<Land> getLandList(){
+        return this.unmodList;
+    }
+
+    /**
+     * 国一覧を更新し、ツリー変更イベントをリスナに投げる。
+     *
+     * <p>村一覧はまだ読み込まれない。
+     *
+     * <p>実際の読み込み処理は一度のみ。
      */
-    // TODO static にできない?
     public void loadLandList(){
         if(this.isLandListLoaded) return;
 
         this.landList.clear();
 
         List<LandDef> landDefList = CoreData.getLandDefList();
-        landDefList.stream().map((landDef) ->
+        landDefList.stream().map(landDef ->
             new Land(landDef)
-        ).forEachOrdered((land) -> {
+        ).forEachOrdered(land -> {
             this.landList.add(land);
         });
 
@@ -101,19 +158,25 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
     }
 
     /**
-     * ツリー内容が更新された事をリスナーに通知する。
+     * 指定した国の村一覧でツリーリストを更新し、
+     * 更新イベントをリスナに投げる。
+     *
+     * @param land 国
      */
-    private void fireLandListChanged(){
-        int size = this.landList.size();
-        int[] childIndices = new int[size];
-        for(int ct = 0; ct < size; ct++){
-            int index = ct;
-            childIndices[ct] = index;
-        }
+    public void updateVillageList(Land land){
+        List<VillageSection> sectionList =
+                getSectionList(land, SECTION_INTERVAL);
+        this.sectionMap.put(land, sectionList);
 
-        Object[] children = this.landList.toArray();
+        int[] childIndices = new int[sectionList.size()];
+        for(int ct = 0; ct < childIndices.length; ct++){
+            childIndices[ct] = ct;
+        }
+        Object[] children = sectionList.toArray();
 
         TreePath treePath = new TreePath(ROOT);
+        treePath = treePath.pathByAddingChild(land);
+
         TreeModelEvent event = new TreeModelEvent(this,
                                                   treePath,
                                                   childIndices,
@@ -125,7 +188,9 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
 
     /**
      * ツリーの並び順を設定する。
-     * 場合によってはTreeModelEventが発生する。
+     *
+     * <p>場合によってはTreeModelEventが発生する。
+     *
      * @param ascending trueなら昇順
      */
     public void setAscending(boolean ascending){
@@ -139,26 +204,29 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
 
     /**
      * {@inheritDoc}
-     * @param l {@inheritDoc}
+     *
+     * @param lst {@inheritDoc}
      */
     @Override
-    public void addTreeModelListener(TreeModelListener l){
-        this.listeners.add(TreeModelListener.class, l);
+    public void addTreeModelListener(TreeModelListener lst){
+        this.listeners.add(TreeModelListener.class, lst);
         return;
     }
 
     /**
      * {@inheritDoc}
-     * @param l {@inheritDoc}
+     *
+     * @param lst {@inheritDoc}
      */
     @Override
-    public void removeTreeModelListener(TreeModelListener l){
-        this.listeners.remove(TreeModelListener.class, l);
+    public void removeTreeModelListener(TreeModelListener lst){
+        this.listeners.remove(TreeModelListener.class, lst);
         return;
     }
 
     /**
      * 登録中のリスナーのリストを得る。
+     *
      * @return リスナーのリスト
      */
     private TreeModelListener[] getTreeModelListeners(){
@@ -167,6 +235,7 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
 
     /**
      * 全リスナーにイベントを送出する。
+     *
      * @param event ツリーイベント
      */
     protected void fireTreeStructureChanged(TreeModelEvent event){
@@ -177,15 +246,31 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
     }
 
     /**
-     * 国リストを得る。
-     * @return 国のリスト
+     * ツリー内容の国一覧が更新された事をリスナーに通知する。
      */
-    public List<Land> getLandList(){
-        return this.unmodList;
+    private void fireLandListChanged(){
+        int size = this.landList.size();
+        int[] childIndices = new int[size];
+        for(int ct = 0; ct < size; ct++){
+            int index = ct;
+            childIndices[ct] = index;
+        }
+
+        Object[] children = this.landList.toArray();
+
+        TreePath treePath = new TreePath(ROOT);
+        TreeModelEvent event = new TreeModelEvent(this,
+                                                  treePath,
+                                                  childIndices,
+                                                  children     );
+        fireTreeStructureChanged(event);
+
+        return;
     }
 
     /**
      * {@inheritDoc}
+     *
      * @param parent {@inheritDoc}
      * @param index {@inheritDoc}
      * @return {@inheritDoc}
@@ -195,13 +280,14 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
         if(index < 0)                      return null;
         if(index >= getChildCount(parent)) return null;
 
-        if(parent == ROOT){
+        if(isRoot(parent)){
             List<Land> list = getLandList();
             int landIndex = index;
             if( ! this.ascending) landIndex = list.size() - index - 1;
             Land land = list.get(landIndex);
             return land;
         }
+
         if(parent instanceof Land){
             Land land = (Land) parent;
             List<VillageSection> sectionList = this.sectionMap.get(land);
@@ -210,6 +296,7 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
             VillageSection section = sectionList.get(sectIndex);
             return section;
         }
+
         if(parent instanceof VillageSection){
             VillageSection section = (VillageSection) parent;
             int vilIndex = index;
@@ -219,34 +306,40 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
             Village village = section.getVillage(vilIndex);
             return village;
         }
+
         return null;
     }
 
     /**
      * {@inheritDoc}
+     *
      * @param parent {@inheritDoc}
      * @return {@inheritDoc}
      */
     @Override
     public int getChildCount(Object parent){
-        if(parent == ROOT){
+        if(isRoot(parent)){
             return getLandList().size();
         }
+
         if(parent instanceof Land){
             Land land = (Land) parent;
             List<VillageSection> sectionList = this.sectionMap.get(land);
             if(sectionList == null) return 0;
             return sectionList.size();
         }
+
         if(parent instanceof VillageSection){
             VillageSection section = (VillageSection) parent;
             return section.getVillageCount();
         }
+
         return 0;
     }
 
     /**
      * {@inheritDoc}
+     *
      * @param parent {@inheritDoc}
      * @param child {@inheritDoc}
      * @return {@inheritDoc}
@@ -254,12 +347,14 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
     @Override
     public int getIndexOfChild(Object parent, Object child){
         if(child == null) return -1;
-        if(parent == ROOT){
+
+        if(isRoot(parent)){
             List<Land> list = getLandList();
             int index = list.indexOf(child);
             if( ! this.ascending) index = list.size() - index - 1;
             return index;
         }
+
         if(parent instanceof Land){
             Land land = (Land) parent;
             List<VillageSection> sectionList = this.sectionMap.get(land);
@@ -267,6 +362,7 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
             if( ! this.ascending) index = sectionList.size() - index - 1;
             return index;
         }
+
         if(parent instanceof VillageSection){
             VillageSection section = (VillageSection) parent;
             int index = section.getIndexOfVillage(child);
@@ -275,11 +371,13 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
             }
             return index;
         }
+
         return -1;
     }
 
     /**
      * {@inheritDoc}
+     *
      * @return {@inheritDoc}
      */
     @Override
@@ -289,12 +387,13 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
 
     /**
      * {@inheritDoc}
+     *
      * @param node {@inheritDoc}
      * @return {@inheritDoc}
      */
     @Override
     public boolean isLeaf(Object node){
-        if(node == ROOT)                   return false;
+        if(isRoot(node))                   return false;
         if(node instanceof Land)           return false;
         if(node instanceof VillageSection) return false;
         if(node instanceof Village)        return true;
@@ -303,7 +402,9 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
 
     /**
      * {@inheritDoc}
-     * ※ たぶん使わないので必ず失敗させている。
+     *
+     * <p>※ たぶん使わないので必ず失敗させている。
+     *
      * @param path {@inheritDoc}
      * @param newValue {@inheritDoc}
      */
@@ -312,121 +413,101 @@ public class LandsTreeModel implements TreeModel{ // ComboBoxModelも付ける
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
+
     /**
      * 村IDで範囲指定した、村のセクション集合。国-村間の中間ツリー。
+     *
      * @see javax.swing.tree.TreeModel
      */
     private static final class VillageSection{
 
-        private final int startID;
-        private final int endID;
         private final String prefix;
+        private final int startId;
+        private final int endId;
 
-        private final List<Village> villageList = new LinkedList<>();
+        private final List<Village> villageList;
 
 
         /**
          * セクション集合を生成する。
-         * @param land 国
-         * @param startID 開始村ID
-         * @param endID 終了村ID
+         *
+         * @param pfx 国名プレフィクス
+         * @param startId 区間開始村ID
+         * @param endId 区間終了村ID
+         * @param spanList 村の区間リスト
          * @throws java.lang.IndexOutOfBoundsException IDの範囲指定が変
          */
-        private VillageSection(Land land, int startID, int endID)
+        VillageSection(
+                String pfx, int startId, int endId, List<Village> spanList)
                 throws IndexOutOfBoundsException{
             super();
 
-            if(startID < 0 || startID > endID){
+            if(startId < 0 || startId > endId){
                 throw new IndexOutOfBoundsException();
             }
 
-            this.startID = startID;
-            this.endID = endID;
-            this.prefix = land.getLandDef().getLandPrefix();
+            this.prefix = pfx;
+            this.startId = startId;
+            this.endId = endId;
 
-            for(Village village : land.getVillageList()){
-                int id = village.getVillageIDNum();
-                if(startID <= id && id <= endID){
-                    this.villageList.add(village);
-                }
-            }
+            List<Village> newList = new ArrayList<>(spanList);
+            this.villageList = Collections.unmodifiableList(newList);
+
+            assert this.endId - this.startId + 1 >= this.villageList.size();
 
             return;
         }
 
 
         /**
-         * 与えられた国の全ての村を、指定されたinterval間隔でセクション化する。
-         * @param land 国
-         * @param interval セクションの間隔
-         * @return セクションのリスト
-         * @throws java.lang.IllegalArgumentException intervalが正でない
-         */
-        private static List<VillageSection> getSectionList(Land land,
-                                                             int interval )
-                throws IllegalArgumentException{
-            if(interval <= 0){
-                throw new IllegalArgumentException();
-            }
-
-            List<Village> villageList = land.getVillageList();
-            Village village1st = villageList.get(0);
-            Village villageLast = villageList.get(villageList.size() - 1);
-
-            int startID = village1st.getVillageIDNum();
-            int endID = villageLast.getVillageIDNum();
-
-            List<VillageSection> result = new LinkedList<>();
-
-            int fixedStart = startID / interval * interval;
-            for(int ct = fixedStart; ct <= endID; ct += interval){
-                VillageSection section =
-                        new VillageSection(land, ct, ct + interval - 1);
-                result.add(section);
-            }
-
-            return Collections.unmodifiableList(result);
-        }
-
-        /**
-         * セクションに含まれる村の総数を返す。
+         * セクション内に含まれる村の総数を返す。
+         *
+         * <p>ほとんどの場合はintervalと同じ数。
+         *
          * @return 村の総数
          */
-        private int getVillageCount(){
+        int getVillageCount(){
             return this.villageList.size();
         }
 
         /**
-         * セクションに含まれるindex番目の村を返す。
+         * セクション内に含まれるindex番目の村を返す。
+         *
          * @param index インデックス
          * @return index番目の村
          */
-        private Village getVillage(int index){
+        Village getVillage(int index){
             return this.villageList.get(index);
         }
 
         /**
-         * セクションにおける、指定された子(村)のインデックス位置を返す。
+         * セクション内における、指定された子(村)のインデックス位置を返す。
+         *
          * @param child 子
          * @return インデックス位置
          */
-        private int getIndexOfVillage(Object child){
+        int getIndexOfVillage(Object child){
             return this.villageList.indexOf(child);
         }
 
         /**
          * セクションの文字列表記。
-         * JTree描画に反映される。
+         *
+         * <p>JTree描画に反映される。
+         *
+         * <p>例:「G800 ~ G899」
+         *
          * @return 文字列表記
          */
         @Override
         public String toString(){
             StringBuilder result = new StringBuilder();
-            result.append(this.prefix).append(this.startID);
+            result.append(this.prefix).append(this.startId);
             result.append(" ~ ");
-            result.append(this.prefix).append(this.endID);
+            result.append(this.prefix).append(this.endId);
             return result.toString();
         }
+
     }
 
 }
index a8aff89..7fd6fff 100644 (file)
@@ -8,7 +8,6 @@
 package jp.sfjp.jindolf.view;
 
 import java.awt.BorderLayout;
-import java.awt.EventQueue;
 import java.awt.Insets;
 import javax.swing.BorderFactory;
 import javax.swing.Icon;
@@ -186,6 +185,7 @@ public class LandsTree extends JPanel{
 
     /**
      * 管理下のLandsTreeModelを返す。
+     *
      * @return LandsTreeModel
      */
     private LandsTreeModel getLandsModel(){
@@ -198,36 +198,39 @@ public class LandsTree extends JPanel{
 
     /**
      * Tree表示順を反転させる。
+     *
+     * <p>昇順/降順ボタンも切り替わる。
+     *
+     * <p>選択中のツリー要素があれば選択は保持される。
+     *
      * @return 反転後が昇順ならtrue
      */
     private boolean toggleTreeOrder(){
         this.ascending = ! this.ascending;
 
+        String newTip;
+        Icon newIcon;
         if(this.ascending){
-            this.orderButton.setToolTipText(TIP_ASCEND);
-            this.orderButton.setIcon(ICON_ASCEND);
+            newTip = TIP_ASCEND;
+            newIcon = ICON_ASCEND;
         }else{
-            this.orderButton.setToolTipText(TIP_DESCEND);
-            this.orderButton.setIcon(ICON_DESCEND);
+            newTip = TIP_DESCEND;
+            newIcon = ICON_DESCEND;
         }
+        this.orderButton.setToolTipText(newTip);
+        this.orderButton.setIcon(newIcon);
 
-        final TreePath lastPath = this.treeView.getSelectionPath();
+        TreePath lastPath = this.treeView.getSelectionPath();
 
         LandsTreeModel model = getLandsModel();
         if(model != null){
             model.setAscending(this.ascending);
         }
 
-        EventQueue.invokeLater(new Runnable(){
-            @Override
-            public void run(){
-                if(lastPath != null){
-                    LandsTree.this.treeView.setSelectionPath(lastPath);
-                    LandsTree.this.treeView.scrollPathToVisible(lastPath);
-                }
-                return;
-            }
-        });
+        if(lastPath != null){
+            this.treeView.setSelectionPath(lastPath);
+            this.treeView.scrollPathToVisible(lastPath);
+        }
 
         return this.ascending;
     }