OSDN Git Service

speed up Village node drawing.
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / data / LandsTreeModel.java
1 /*
2  * model of lands for JTree view
3  *
4  * License : The MIT License
5  * Copyright(c) 2008 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.data;
9
10 import java.text.MessageFormat;
11 import java.util.ArrayList;
12 import java.util.Collections;
13 import java.util.HashMap;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Objects;
17 import javax.swing.event.EventListenerList;
18 import javax.swing.event.TreeModelEvent;
19 import javax.swing.event.TreeModelListener;
20 import javax.swing.tree.TreeModel;
21 import javax.swing.tree.TreePath;
22 import jp.sourceforge.jindolf.corelib.LandDef;
23
24 /**
25  * {@link javax.swing.JTree}のモデルとして国一覧と村一覧を管理。
26  *
27  * <p>ツリー階層は ROOT - 国 - 範囲セクション - 村 の4階層。
28  *
29  * <p>昇順/降順の切り替えをサポート。
30  */
31 public class LandsTreeModel implements TreeModel{
32
33     private static final Object ROOT = new Object();
34     private static final int SECTION_INTERVAL = 100;
35
36
37     private final EventListenerList listeners;
38
39     private final List<Land> landList;
40     private final Map<Land, List<VillageSection> > sectionMap;
41
42     private boolean ascending = false;
43
44
45     /**
46      * コンストラクタ。
47      */
48     public LandsTreeModel(){
49         super();
50
51         this.listeners = new EventListenerList();
52
53         this.landList = buildLandList();
54         this.sectionMap = new HashMap<>();
55
56         return;
57     }
58
59
60     /**
61      * ツリーのルートオブジェクトか否か判定する。
62      *
63      * @param obj オブジェクト
64      * @return ルートならtrue
65      */
66     private static boolean isRoot(Object obj){
67         boolean result = Objects.equals(ROOT, obj);
68         return result;
69     }
70
71     /**
72      * 国一覧を読み込む。
73      *
74      * <p>村一覧はまだ読み込まれない。
75      *
76      * @return 国リスト
77      */
78     private static List<Land> buildLandList(){
79         List<LandDef> landDefList = CoreData.getLandDefList();
80         List<Land> newList = new ArrayList<>(landDefList.size());
81
82         landDefList.stream().map(landDef ->
83             new Land(landDef)
84         ).forEachOrdered(land -> {
85             newList.add(land);
86         });
87
88         return Collections.unmodifiableList(newList);
89     }
90
91     /**
92      * 与えられた国の全ての村を指定されたinterval間隔で格納するために、
93      * 範囲セクションのリストを生成する。
94      *
95      * @param land 国
96      * @param interval 範囲セクション間の村ID間隔
97      * @return 範囲セクションのリスト
98      * @throws java.lang.IllegalArgumentException intervalが正でない
99      */
100     private static List<VillageSection> getSectionList(Land land,
101                                                        int interval )
102             throws IllegalArgumentException{
103         if(interval <= 0){
104             throw new IllegalArgumentException();
105         }
106
107         String pfx = land.getLandDef().getLandPrefix();
108         List<Village> span = new ArrayList<>(interval);
109
110         List<VillageSection> result = new ArrayList<>(2500 / interval);
111
112         boolean loop1st = true;
113         int rangeStart = -1;
114         int rangeEnd = -1;
115
116         for(Village village : land.getVillageList()){
117             int vid = village.getVillageIDNum();
118
119             if(loop1st){
120                 rangeStart = vid / interval * interval;
121                 rangeEnd = rangeStart + interval - 1;
122                 loop1st = false;
123             }
124
125             if(rangeEnd < vid){
126                 VillageSection section = new VillageSection(
127                         pfx, rangeStart, rangeEnd, span);
128                 span.clear();
129                 result.add(section);
130
131                 rangeStart = vid / interval * interval;
132                 rangeEnd = rangeStart + interval - 1;
133             }
134
135             span.add(village);
136         }
137
138         if( ! span.isEmpty()){
139             VillageSection section = new VillageSection(
140                     pfx, rangeStart, rangeEnd, span);
141             span.clear();
142             result.add(section);
143         }
144
145         return result;
146     }
147
148
149     /**
150      * 国リストを得る。
151      *
152      * @return 国のリスト
153      */
154     public List<Land> getLandList(){
155         return this.landList;
156     }
157
158     /**
159      * 指定した国の村一覧でツリーリストを更新し、
160      * 更新イベントをリスナに投げる。
161      *
162      * <p>2020-04現在、もはや村一覧が増減することはない。
163      *
164      * @param land 国
165      */
166     public void updateVillageList(Land land){
167         List<VillageSection> sectionList =
168                 getSectionList(land, SECTION_INTERVAL);
169         this.sectionMap.put(land, sectionList);
170
171         int[] childIndices = new int[sectionList.size()];
172         for(int ct = 0; ct < childIndices.length; ct++){
173             childIndices[ct] = ct;
174         }
175         Object[] children = sectionList.toArray();
176
177         TreePath treePath = new TreePath(ROOT);
178         treePath = treePath.pathByAddingChild(land);
179
180         TreeModelEvent event = new TreeModelEvent(this,
181                                                   treePath,
182                                                   childIndices,
183                                                   children     );
184         fireTreeStructureChanged(event);
185
186         return;
187     }
188
189     /**
190      * ツリーの並び順を設定する。
191      *
192      * <p>場合によってはTreeModelEventが発生する。
193      *
194      * @param ascending trueなら昇順
195      */
196     public void setAscending(boolean ascending){
197         if(this.ascending == ascending) return;
198
199         this.ascending = ascending;
200         fireLandListChanged();
201
202         return;
203     }
204
205     /**
206      * {@inheritDoc}
207      *
208      * @param lst {@inheritDoc}
209      */
210     @Override
211     public void addTreeModelListener(TreeModelListener lst){
212         this.listeners.add(TreeModelListener.class, lst);
213         return;
214     }
215
216     /**
217      * {@inheritDoc}
218      *
219      * @param lst {@inheritDoc}
220      */
221     @Override
222     public void removeTreeModelListener(TreeModelListener lst){
223         this.listeners.remove(TreeModelListener.class, lst);
224         return;
225     }
226
227     /**
228      * 登録中のリスナーのリストを得る。
229      *
230      * @return リスナーのリスト
231      */
232     private TreeModelListener[] getTreeModelListeners(){
233         return this.listeners.getListeners(TreeModelListener.class);
234     }
235
236     /**
237      * 全リスナーにイベントを送出する。
238      *
239      * @param event ツリーイベント
240      */
241     protected void fireTreeStructureChanged(TreeModelEvent event){
242         for(TreeModelListener listener : getTreeModelListeners()){
243             listener.treeStructureChanged(event);
244         }
245         return;
246     }
247
248     /**
249      * ツリー内容の国一覧が更新された事をリスナーに通知する。
250      */
251     private void fireLandListChanged(){
252         int size = getLandList().size();
253         int[] childIndices = new int[size];
254         for(int ct = 0; ct < size; ct++){
255             int index = ct;
256             childIndices[ct] = index;
257         }
258
259         Object[] children = getLandList().toArray();
260
261         TreePath treePath = new TreePath(ROOT);
262         TreeModelEvent event = new TreeModelEvent(this,
263                                                   treePath,
264                                                   childIndices,
265                                                   children     );
266         fireTreeStructureChanged(event);
267
268         return;
269     }
270
271     /**
272      * {@inheritDoc}
273      *
274      * @param parent {@inheritDoc}
275      * @param index {@inheritDoc}
276      * @return {@inheritDoc}
277      */
278     @Override
279     public Object getChild(Object parent, int index){
280         if(index < 0)                      return null;
281         if(index >= getChildCount(parent)) return null;
282
283         Object result = null;
284
285         if(isRoot(parent)){
286             List<Land> list = getLandList();
287             int landIndex = index;
288             if( ! this.ascending) landIndex = list.size() - index - 1;
289             Land land = list.get(landIndex);
290             result = land;
291         }else if(parent instanceof Land){
292             Land land = (Land) parent;
293             List<VillageSection> sectionList = this.sectionMap.get(land);
294             int sectIndex = index;
295             if( ! this.ascending) sectIndex = sectionList.size() - index - 1;
296             VillageSection section = sectionList.get(sectIndex);
297             result = section;
298         }else if(parent instanceof VillageSection){
299             VillageSection section = (VillageSection) parent;
300             int vilIndex = index;
301             if( ! this.ascending){
302                 vilIndex = section.getVillageCount() - index - 1;
303             }
304             Village village = section.getVillage(vilIndex);
305             result = village;
306         }
307
308         return result;
309     }
310
311     /**
312      * {@inheritDoc}
313      *
314      * @param parent {@inheritDoc}
315      * @return {@inheritDoc}
316      */
317     @Override
318     public int getChildCount(Object parent){
319         int result = 0;
320
321         if(isRoot(parent)){
322             result = getLandList().size();
323         }else if(parent instanceof Land){
324             Land land = (Land) parent;
325             List<VillageSection> sectionList = this.sectionMap.get(land);
326             if(sectionList != null){
327                 result = sectionList.size();
328             }
329         }else if(parent instanceof VillageSection){
330             VillageSection section = (VillageSection) parent;
331             result = section.getVillageCount();
332         }
333
334         return result;
335     }
336
337     /**
338      * {@inheritDoc}
339      *
340      * @param parent {@inheritDoc}
341      * @param child {@inheritDoc}
342      * @return {@inheritDoc}
343      */
344     @Override
345     public int getIndexOfChild(Object parent, Object child){
346         if(child == null) return -1;
347
348         int result = -1;
349
350         if(isRoot(parent)){
351             List<Land> list = getLandList();
352             int index = list.indexOf(child);
353             if( ! this.ascending) index = list.size() - index - 1;
354             result = index;
355         }else if(parent instanceof Land){
356             Land land = (Land) parent;
357             List<VillageSection> sectionList = this.sectionMap.get(land);
358             int index = sectionList.indexOf(child);
359             if( ! this.ascending) index = sectionList.size() - index - 1;
360             result = index;
361         }else if(parent instanceof VillageSection){
362             VillageSection section = (VillageSection) parent;
363             int index = section.getIndexOfVillage(child);
364             if( ! this.ascending){
365                 index = section.getVillageCount() - index - 1;
366             }
367             result = index;
368         }
369
370         return result;
371     }
372
373     /**
374      * {@inheritDoc}
375      *
376      * @return {@inheritDoc}
377      */
378     @Override
379     public Object getRoot(){
380         return ROOT;
381     }
382
383     /**
384      * {@inheritDoc}
385      *
386      * @param node {@inheritDoc}
387      * @return {@inheritDoc}
388      */
389     @Override
390     public boolean isLeaf(Object node){
391         if(node instanceof Village)        return true;
392         if(node instanceof VillageSection) return false;
393         if(node instanceof Land)           return false;
394         if(isRoot(node))                   return false;
395         return true;
396     }
397
398     /**
399      * {@inheritDoc}
400      *
401      * <p>※ たぶん使わないので必ず失敗させている。
402      *
403      * @param path {@inheritDoc}
404      * @param newValue {@inheritDoc}
405      */
406     @Override
407     public void valueForPathChanged(TreePath path, Object newValue){
408         throw new UnsupportedOperationException("Not supported yet.");
409     }
410
411
412     /**
413      * 村IDで範囲指定した、村のセクション集合。国-村間の中間ツリー。
414      *
415      * @see javax.swing.tree.TreeModel
416      */
417     private static final class VillageSection{
418
419         private static final String FORM_NODE =
420                 "{0}{1,number,#} ~ {0}{2,number,#}";
421         private static final String FORM_NODE_G =
422                 "{0}{1,number,#000} ~ {0}{2,number,#000}";
423
424
425         private final int startId;
426         private final int endId;
427
428         private final String text;
429
430         private final List<Village> villageList;
431
432
433         /**
434          * セクション集合を生成する。
435          *
436          * @param prefix 国名プレフィクス
437          * @param startId 区間開始村ID
438          * @param endId 区間終了村ID
439          * @param spanList 村の区間リスト
440          * @throws java.lang.IndexOutOfBoundsException IDの範囲指定が変
441          */
442         VillageSection(
443                 String prefix, int startId, int endId, List<Village> spanList)
444                 throws IndexOutOfBoundsException{
445             super();
446
447             if(startId < 0 || startId > endId){
448                 throw new IndexOutOfBoundsException();
449             }
450
451             this.startId = startId;
452             this.endId = endId;
453
454             String format;
455             if("G".equals(prefix)) format = FORM_NODE_G;
456             else                   format = FORM_NODE;
457             this.text = MessageFormat.format(
458                     format, prefix, this.startId, this.endId);
459
460             List<Village> newList = new ArrayList<>(spanList);
461             this.villageList = Collections.unmodifiableList(newList);
462
463             assert this.endId - this.startId + 1 >= this.villageList.size();
464
465             return;
466         }
467
468
469         /**
470          * セクション内に含まれる村の総数を返す。
471          *
472          * <p>ほとんどの場合はintervalと同じ数。
473          *
474          * @return 村の総数
475          */
476         int getVillageCount(){
477             return this.villageList.size();
478         }
479
480         /**
481          * セクション内に含まれるindex番目の村を返す。
482          *
483          * @param index インデックス
484          * @return index番目の村
485          */
486         Village getVillage(int index){
487             return this.villageList.get(index);
488         }
489
490         /**
491          * セクション内における、指定された子(村)のインデックス位置を返す。
492          *
493          * @param child 子
494          * @return インデックス位置
495          */
496         int getIndexOfVillage(Object child){
497             return this.villageList.indexOf(child);
498         }
499
500         /**
501          * セクションの文字列表記。
502          *
503          * <p>JTree描画に反映される。
504          *
505          * <p>例:「G800 ~ G899」
506          *
507          * @return 文字列表記
508          */
509         @Override
510         public String toString(){
511             return this.text;
512         }
513
514     }
515
516 }