OSDN Git Service

Merge release/v3.303.106
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / data / LandsModel.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.io.IOException;
11 import java.net.URISyntaxException;
12 import java.util.Collections;
13 import java.util.HashMap;
14 import java.util.LinkedList;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.logging.Level;
18 import java.util.logging.Logger;
19 import javax.swing.event.EventListenerList;
20 import javax.swing.event.TreeModelEvent;
21 import javax.swing.event.TreeModelListener;
22 import javax.swing.tree.TreeModel;
23 import javax.swing.tree.TreePath;
24 import javax.xml.parsers.DocumentBuilder;
25 import javax.xml.parsers.ParserConfigurationException;
26 import jp.sfjp.jindolf.dxchg.XmlUtils;
27 import jp.sourceforge.jindolf.corelib.LandDef;
28 import org.xml.sax.SAXException;
29
30 /**
31  * 国の集合。あらゆるデータモデルの大元。
32  * 国一覧と村一覧を管理。
33  * JTreeのモデルも兼用。
34  */
35 public class LandsModel implements TreeModel{ // ComboBoxModelも付けるか?
36
37     private static final String ROOT = "ROOT";
38     private static final int SECTION_INTERVAL = 100;
39
40     private static final Logger LOGGER = Logger.getAnonymousLogger();
41
42
43     private final List<Land> landList = new LinkedList<>();
44     private final List<Land> unmodList =
45             Collections.unmodifiableList(this.landList);
46     private final Map<Land, List<VillageSection>> sectionMap =
47             new HashMap<>();
48     private boolean isLandListLoaded = false;
49
50     private final EventListenerList listeners = new EventListenerList();
51
52     private boolean ascending = false;
53
54     /**
55      * コンストラクタ。
56      * この時点ではまだ国一覧が読み込まれない。
57      */
58     public LandsModel(){
59         super();
60         return;
61     }
62
63     /**
64      * 指定した国の村一覧を更新しイベントを投げる。
65      * @param land 国
66      */
67     public void updateVillageList(Land land){
68         List<VillageSection> sectionList =
69                 VillageSection.getSectionList(land, SECTION_INTERVAL);
70         this.sectionMap.put(land, sectionList);
71
72         int[] childIndices = new int[sectionList.size()];
73         for(int ct = 0; ct < childIndices.length; ct++){
74             childIndices[ct] = ct;
75         }
76         Object[] children = sectionList.toArray();
77
78         Object[] path = {ROOT, land};
79         TreePath treePath = new TreePath(path);
80         TreeModelEvent event = new TreeModelEvent(this,
81                                                   treePath,
82                                                   childIndices,
83                                                   children     );
84         fireTreeStructureChanged(event);
85
86         return;
87     }
88
89     /**
90      * 国一覧を読み込む。
91      */
92     // TODO static にできない?
93     public void loadLandList(){
94         if(this.isLandListLoaded) return;
95
96         this.landList.clear();
97
98         List<LandDef> landDefList;
99         try{
100             DocumentBuilder builder = XmlUtils.createDocumentBuilder();
101             landDefList = LandDef.buildLandDefList(builder);
102         }catch(   IOException
103                 | SAXException
104                 | URISyntaxException
105                 | ParserConfigurationException
106                 e){
107             LOGGER.log(Level.SEVERE, "failed to load land list", e);
108             return;
109         }
110
111         for(LandDef landDef : landDefList){
112             Land land = new Land(landDef);
113             this.landList.add(land);
114         }
115
116         this.isLandListLoaded = true;
117
118         fireLandListChanged();
119
120         return;
121     }
122
123     /**
124      * ツリー内容が更新された事をリスナーに通知する。
125      */
126     private void fireLandListChanged(){
127         int size = this.landList.size();
128         int[] childIndices = new int[size];
129         for(int ct = 0; ct < size; ct++){
130             int index = ct;
131             childIndices[ct] = index;
132         }
133
134         Object[] children = this.landList.toArray();
135
136         TreePath treePath = new TreePath(ROOT);
137         TreeModelEvent event = new TreeModelEvent(this,
138                                                   treePath,
139                                                   childIndices,
140                                                   children     );
141         fireTreeStructureChanged(event);
142
143         return;
144     }
145
146     /**
147      * ツリーの並び順を設定する。
148      * 場合によってはTreeModelEventが発生する。
149      * @param ascending trueなら昇順
150      */
151     public void setAscending(boolean ascending){
152         if(this.ascending == ascending) return;
153
154         this.ascending = ascending;
155         fireLandListChanged();
156
157         return;
158     }
159
160     /**
161      * {@inheritDoc}
162      * @param l {@inheritDoc}
163      */
164     @Override
165     public void addTreeModelListener(TreeModelListener l){
166         this.listeners.add(TreeModelListener.class, l);
167         return;
168     }
169
170     /**
171      * {@inheritDoc}
172      * @param l {@inheritDoc}
173      */
174     @Override
175     public void removeTreeModelListener(TreeModelListener l){
176         this.listeners.remove(TreeModelListener.class, l);
177         return;
178     }
179
180     /**
181      * 登録中のリスナーのリストを得る。
182      * @return リスナーのリスト
183      */
184     private TreeModelListener[] getTreeModelListeners(){
185         return this.listeners.getListeners(TreeModelListener.class);
186     }
187
188     /**
189      * 全リスナーにイベントを送出する。
190      * @param event ツリーイベント
191      */
192     protected void fireTreeStructureChanged(TreeModelEvent event){
193         for(TreeModelListener listener : getTreeModelListeners()){
194             listener.treeStructureChanged(event);
195         }
196         return;
197     }
198
199     /**
200      * 国リストを得る。
201      * @return 国のリスト
202      */
203     public List<Land> getLandList(){
204         return this.unmodList;
205     }
206
207     /**
208      * {@inheritDoc}
209      * @param parent {@inheritDoc}
210      * @param index {@inheritDoc}
211      * @return {@inheritDoc}
212      */
213     @Override
214     public Object getChild(Object parent, int index){
215         if(index < 0)                      return null;
216         if(index >= getChildCount(parent)) return null;
217
218         if(parent == ROOT){
219             List<Land> list = getLandList();
220             int landIndex = index;
221             if( ! this.ascending) landIndex = list.size() - index - 1;
222             Land land = list.get(landIndex);
223             return land;
224         }
225         if(parent instanceof Land){
226             Land land = (Land) parent;
227             List<VillageSection> sectionList = this.sectionMap.get(land);
228             int sectIndex = index;
229             if( ! this.ascending) sectIndex = sectionList.size() - index - 1;
230             VillageSection section = sectionList.get(sectIndex);
231             return section;
232         }
233         if(parent instanceof VillageSection){
234             VillageSection section = (VillageSection) parent;
235             int vilIndex = index;
236             if( ! this.ascending){
237                 vilIndex = section.getVillageCount() - index - 1;
238             }
239             Village village = section.getVillage(vilIndex);
240             return village;
241         }
242         return null;
243     }
244
245     /**
246      * {@inheritDoc}
247      * @param parent {@inheritDoc}
248      * @return {@inheritDoc}
249      */
250     @Override
251     public int getChildCount(Object parent){
252         if(parent == ROOT){
253             return getLandList().size();
254         }
255         if(parent instanceof Land){
256             Land land = (Land) parent;
257             List<VillageSection> sectionList = this.sectionMap.get(land);
258             if(sectionList == null) return 0;
259             return sectionList.size();
260         }
261         if(parent instanceof VillageSection){
262             VillageSection section = (VillageSection) parent;
263             return section.getVillageCount();
264         }
265         return 0;
266     }
267
268     /**
269      * {@inheritDoc}
270      * @param parent {@inheritDoc}
271      * @param child {@inheritDoc}
272      * @return {@inheritDoc}
273      */
274     @Override
275     public int getIndexOfChild(Object parent, Object child){
276         if(child == null) return -1;
277         if(parent == ROOT){
278             List<Land> list = getLandList();
279             int index = list.indexOf(child);
280             if( ! this.ascending) index = list.size() - index - 1;
281             return index;
282         }
283         if(parent instanceof Land){
284             Land land = (Land) parent;
285             List<VillageSection> sectionList = this.sectionMap.get(land);
286             int index = sectionList.indexOf(child);
287             if( ! this.ascending) index = sectionList.size() - index - 1;
288             return index;
289         }
290         if(parent instanceof VillageSection){
291             VillageSection section = (VillageSection) parent;
292             int index = section.getIndexOfVillage(child);
293             if( ! this.ascending){
294                 index = section.getVillageCount() - index - 1;
295             }
296             return index;
297         }
298         return -1;
299     }
300
301     /**
302      * {@inheritDoc}
303      * @return {@inheritDoc}
304      */
305     @Override
306     public Object getRoot(){
307         return ROOT;
308     }
309
310     /**
311      * {@inheritDoc}
312      * @param node {@inheritDoc}
313      * @return {@inheritDoc}
314      */
315     @Override
316     public boolean isLeaf(Object node){
317         if(node == ROOT)                   return false;
318         if(node instanceof Land)           return false;
319         if(node instanceof VillageSection) return false;
320         if(node instanceof Village)        return true;
321         return true;
322     }
323
324     /**
325      * {@inheritDoc}
326      * ※ たぶん使わないので必ず失敗させている。
327      * @param path {@inheritDoc}
328      * @param newValue {@inheritDoc}
329      */
330     @Override
331     public void valueForPathChanged(TreePath path, Object newValue){
332         throw new UnsupportedOperationException("Not supported yet.");
333     }
334
335     /**
336      * 村IDで範囲指定した、村のセクション集合。国-村間の中間ツリー。
337      * @see javax.swing.tree.TreeModel
338      */
339     private static final class VillageSection{
340
341         private final int startID;
342         private final int endID;
343         private final String prefix;
344
345         private final List<Village> villageList = new LinkedList<>();
346
347
348         /**
349          * セクション集合を生成する。
350          * @param land 国
351          * @param startID 開始村ID
352          * @param endID 終了村ID
353          * @throws java.lang.IndexOutOfBoundsException IDの範囲指定が変
354          */
355         private VillageSection(Land land, int startID, int endID)
356                 throws IndexOutOfBoundsException{
357             super();
358
359             if(startID < 0 || startID > endID){
360                 throw new IndexOutOfBoundsException();
361             }
362
363             this.startID = startID;
364             this.endID = endID;
365             this.prefix = land.getLandDef().getLandPrefix();
366
367             for(Village village : land.getVillageList()){
368                 int id = village.getVillageIDNum();
369                 if(startID <= id && id <= endID){
370                     this.villageList.add(village);
371                 }
372             }
373
374             return;
375         }
376
377
378         /**
379          * 与えられた国の全ての村を、指定されたinterval間隔でセクション化する。
380          * @param land 国
381          * @param interval セクションの間隔
382          * @return セクションのリスト
383          * @throws java.lang.IllegalArgumentException intervalが正でない
384          */
385         private static List<VillageSection> getSectionList(Land land,
386                                                              int interval )
387                 throws IllegalArgumentException{
388             if(interval <= 0){
389                 throw new IllegalArgumentException();
390             }
391
392             List<Village> villageList = land.getVillageList();
393             Village village1st = villageList.get(0);
394             Village villageLast = villageList.get(villageList.size() - 1);
395
396             int startID = village1st.getVillageIDNum();
397             int endID = villageLast.getVillageIDNum();
398
399             List<VillageSection> result = new LinkedList<>();
400
401             int fixedStart = startID / interval * interval;
402             for(int ct = fixedStart; ct <= endID; ct += interval){
403                 VillageSection section =
404                         new VillageSection(land, ct, ct + interval - 1);
405                 result.add(section);
406             }
407
408             return Collections.unmodifiableList(result);
409         }
410
411         /**
412          * セクションに含まれる村の総数を返す。
413          * @return 村の総数
414          */
415         private int getVillageCount(){
416             return this.villageList.size();
417         }
418
419         /**
420          * セクションに含まれるindex番目の村を返す。
421          * @param index インデックス
422          * @return index番目の村
423          */
424         private Village getVillage(int index){
425             return this.villageList.get(index);
426         }
427
428         /**
429          * セクションにおける、指定された子(村)のインデックス位置を返す。
430          * @param child 子
431          * @return インデックス位置
432          */
433         private int getIndexOfVillage(Object child){
434             return this.villageList.indexOf(child);
435         }
436
437         /**
438          * セクションの文字列表記。
439          * JTree描画に反映される。
440          * @return 文字列表記
441          */
442         @Override
443         public String toString(){
444             StringBuilder result = new StringBuilder();
445             result.append(this.prefix).append(this.startID);
446             result.append(" ~ ");
447             result.append(this.prefix).append(this.endID);
448             return result.toString();
449         }
450     }
451
452 }