OSDN Git Service

スタートアップ処理の改善
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / data / Land.java
1 /*
2  * land
3  *
4  * License : The MIT License
5  * Copyright(c) 2008 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.data;
9
10 import java.awt.image.BufferedImage;
11 import java.io.IOException;
12 import java.net.MalformedURLException;
13 import java.net.URI;
14 import java.net.URISyntaxException;
15 import java.net.URL;
16 import java.util.Collections;
17 import java.util.LinkedList;
18 import java.util.List;
19 import java.util.SortedSet;
20 import java.util.TreeSet;
21 import java.util.logging.Level;
22 import java.util.logging.Logger;
23 import jp.sfjp.jindolf.net.HtmlSequence;
24 import jp.sfjp.jindolf.net.ServerAccess;
25 import jp.sourceforge.jindolf.corelib.LandDef;
26 import jp.sourceforge.jindolf.corelib.LandState;
27 import jp.sourceforge.jindolf.corelib.VillageState;
28 import jp.sourceforge.jindolf.parser.DecodedContent;
29 import jp.sourceforge.jindolf.parser.HtmlAdapter;
30 import jp.sourceforge.jindolf.parser.HtmlParseException;
31 import jp.sourceforge.jindolf.parser.HtmlParser;
32 import jp.sourceforge.jindolf.parser.PageType;
33 import jp.sourceforge.jindolf.parser.SeqRange;
34
35 /**
36  * いわゆる「国」。
37  */
38 public class Land {
39
40     // 古国ID
41     private static final String ID_VANILLAWOLF = "wolf";
42
43     private static final Logger LOGGER = Logger.getAnonymousLogger();
44
45
46     private final LandDef landDef;
47     private final ServerAccess serverAccess;
48     private final HtmlParser parser = new HtmlParser();
49     private final VillageListHandler handler = new VillageListHandler();
50
51     private final List<Village> villageList = new LinkedList<Village>();
52
53
54     /**
55      * コンストラクタ。
56      * @param landDef 国定義
57      * @throws java.lang.IllegalArgumentException 不正な国定義
58      */
59     public Land(LandDef landDef) throws IllegalArgumentException{
60         super();
61
62         this.landDef = landDef;
63
64         URL url;
65         try{
66             url = this.landDef.getCgiURI().toURL();
67         }catch(MalformedURLException e){
68             throw new IllegalArgumentException(e);
69         }
70         this.serverAccess = new ServerAccess(url, this.landDef.getEncoding());
71
72         this.parser.setBasicHandler(this.handler);
73
74         return;
75     }
76
77
78     /**
79      * クエリー文字列から特定キーの値を得る。
80      * クエリーの書式例:「a=b&c=d&e=f」この場合キーcの値はd
81      * @param key キー
82      * @param allQuery クエリー
83      * @return 値
84      */
85     public static String getValueFromCGIQueries(String key,
86                                                    String allQuery){
87         String result = null;
88
89         String[] queries = allQuery.split("\\Q&\\E");
90
91         for(String pair : queries){
92             if(pair == null) continue;
93             String[] namevalue = pair.split("\\Q=\\E");
94             if(namevalue == null) continue;
95             if(namevalue.length != 2) continue;
96             String name  = namevalue[0];
97             String value = namevalue[1];
98             if(name == null) continue;
99             if( name.equals(key) ){
100                 result = value;
101                 if(result == null) continue;
102                 if(result.length() <= 0) continue;
103                 break;
104             }
105         }
106
107         return result;
108     }
109
110     /**
111      * AタグのHREF属性値からクエリー部を抽出する。
112      * 「{@literal &amp;}」は「{@literal &}」に解釈される。
113      * @param hrefValue HREF属性値
114      * @return クエリー文字列
115      */
116     public static String getRawQueryFromHREF(CharSequence hrefValue){
117         if(hrefValue == null) return null;
118
119         // HTML 4.01 B.2.2 rule
120         String pureHREF = hrefValue.toString().replace("&amp;", "&");
121
122         URI uri;
123         try{
124             uri = new URI(pureHREF);
125         }catch(URISyntaxException e){
126             LOGGER.warning(
127                      "不正なURI["
128                     + hrefValue
129                     + "]を検出しました");
130             return null;
131         }
132
133         String rawQuery = uri.getRawQuery();
134
135         return rawQuery;
136     }
137
138     /**
139      * AタグのHREF属性値から村IDを得る。
140      * @param hrefValue HREF値
141      * @return village 村ID
142      */
143     public static String getVillageIDFromHREF(CharSequence hrefValue){
144         String rawQuery = getRawQueryFromHREF(hrefValue);
145         if(rawQuery == null) return null;
146
147         String villageID = getValueFromCGIQueries("vid", rawQuery);
148         if(villageID == null) return null;
149         if(villageID.length() <= 0) return null;
150
151         return villageID;
152     }
153
154     /**
155      * 国定義を得る。
156      * @return 国定義
157      */
158     public LandDef getLandDef(){
159         return this.landDef;
160     }
161
162     /**
163      * サーバ接続を返す。
164      * @return ServerAccessインスタンス
165      */
166     public ServerAccess getServerAccess(){
167         return this.serverAccess;
168     }
169
170     /**
171      * 指定されたインデックス位置の村を返す。
172      * @param index 0から始まるインデックス値
173      * @return 村
174      */
175     public Village getVillage(int index){
176         if(index < 0)                  return null;
177         if(index >= getVillageCount()) return null;
178
179         Village result = this.villageList.get(index);
180         return result;
181     }
182
183     /**
184      * 村の総数を返す。
185      * @return 村の総数
186      */
187     public int getVillageCount(){
188         int result = this.villageList.size();
189         return result;
190     }
191
192     /**
193      * 村のリストを返す。
194      * @return 村のリスト
195      */
196     // TODO インスタンス変数でいいはず。
197     public List<Village> getVillageList(){
198         return Collections.unmodifiableList(this.villageList);
199     }
200
201     /**
202      * 絶対または相対URLの指すパーマネントなイメージ画像をダウンロードする。
203      * ※ A,B,D 国の顔アイコンは絶対パスらしい…。
204      * @param imageURL 画像URL文字列
205      * @return 画像イメージ
206      */
207     public BufferedImage downloadImage(String imageURL){
208         ServerAccess server = getServerAccess();
209         BufferedImage image;
210         try{
211             image = server.downloadImage(imageURL);
212         }catch(IOException e){
213             LOGGER.log(Level.WARNING,
214                     "イメージ[" + imageURL + "]"
215                     + "のダウンロードに失敗しました",
216                     e );
217             return null;
218         }
219         return image;
220     }
221
222     /**
223      * 墓アイコンイメージを取得する。
224      * @return 墓アイコンイメージ
225      */
226     public BufferedImage getGraveIconImage(){
227         URI uri = getLandDef().getTombFaceIconURI();
228         BufferedImage result = downloadImage(uri.toASCIIString());
229         return result;
230     }
231
232     /**
233      * 墓アイコンイメージ(大)を取得する。
234      * @return 墓アイコンイメージ(大)
235      */
236     public BufferedImage getGraveBodyImage(){
237         URI uri = getLandDef().getTombBodyIconURI();
238         BufferedImage result = downloadImage(uri.toASCIIString());
239         return result;
240     }
241
242     /**
243      * 村一覧情報をダウンロードする。
244      * リスト元情報は国のトップページと村一覧ページ。
245      * 古国の場合は村一覧にアクセスせずトップページのみ。
246      * 古国以外で村建てをやめた国はトップページにアクセスしない。
247      * 村リストはVillageの実装に従いソートされる。重複する村は排除。
248      * @throws java.io.IOException ネットワーク入出力の異常
249      * @return ソートされた村一覧
250      */
251     public SortedSet<Village> downloadVillageList() throws IOException {
252         LandDef thisLand = getLandDef();
253         LandState state = thisLand.getLandState();
254         boolean isVanillaWolf = thisLand.getLandId().equals(ID_VANILLAWOLF);
255
256         ServerAccess server = getServerAccess();
257
258         // たまに同じ村が複数回出現するので注意!
259         SortedSet<Village> result = new TreeSet<Village>();
260
261         // トップページ
262         if(state.equals(LandState.ACTIVE) || isVanillaWolf){
263             HtmlSequence html = server.getHTMLTopPage();
264             DecodedContent content = html.getContent();
265             try{
266                 this.parser.parseAutomatic(content);
267             }catch(HtmlParseException e){
268                 LOGGER.log(Level.WARNING, "トップページを認識できない", e);
269             }
270             List<Village> list = this.handler.getVillageList();
271             if(list != null){
272                 result.addAll(list);
273             }
274         }
275
276         // 村一覧ページ
277         if( ! isVanillaWolf ){
278             HtmlSequence html = server.getHTMLLandList();
279             DecodedContent content = html.getContent();
280             try{
281                 this.parser.parseAutomatic(content);
282             }catch(HtmlParseException e){
283                 LOGGER.log(Level.WARNING, "村一覧ページを認識できない", e);
284             }
285             List<Village> list = this.handler.getVillageList();
286             if(list != null){
287                 result.addAll(list);
288             }
289         }
290
291         this.parser.reset();
292         this.handler.reset();
293
294         return result;
295     }
296
297     /**
298      * 村リストを更新する。
299      * @param vset ソート済みの村一覧
300      */
301     public void updateVillageList(SortedSet<Village> vset){
302         // TODO 村リスト更新のイベントリスナがあると便利か?
303         this.villageList.clear();
304         this.villageList.addAll(vset);
305         return;
306     }
307
308     /**
309      * 国の文字列表現を返す。
310      * @return 文字列表現
311      */
312     @Override
313     public String toString(){
314         return getLandDef().getLandName();
315     }
316
317     /**
318      * 村一覧取得用ハンドラ。
319      */
320     private class VillageListHandler extends HtmlAdapter{
321
322         private List<Village> villageList = null;
323
324         /**
325          * コンストラクタ。
326          */
327         public VillageListHandler(){
328             super();
329             return;
330         }
331
332         /**
333          * 村一覧を返す。
334          * 再度パースを行うまで呼んではいけない。
335          * @return 村一覧
336          * @throws IllegalStateException パース前に呼び出された。
337          * あるいはパース後すでにリセットされている。
338          */
339         public List<Village> getVillageList() throws IllegalStateException{
340             if(this.villageList == null){
341                 throw new IllegalStateException("パースが必要です。");
342             }
343
344             List<Village> result = this.villageList;
345
346             return result;
347         }
348
349         /**
350          * リセットを行う。
351          * 村一覧は空になる。
352          */
353         public void reset(){
354             this.villageList = null;
355             return;
356         }
357
358         /**
359          * {@inheritDoc}
360          * 村一覧リストが初期化される。
361          * @param content {@inheritDoc}
362          * @throws HtmlParseException {@inheritDoc}
363          */
364         @Override
365         public void startParse(DecodedContent content)
366                 throws HtmlParseException{
367             reset();
368             this.villageList = new LinkedList<Village>();
369             return;
370         }
371
372         /**
373          * {@inheritDoc}
374          * 自動判定の結果がトップページでも村一覧ページでもなければ
375          * 例外を投げる。
376          * @param type {@inheritDoc}
377          * @throws HtmlParseException {@inheritDoc} 意図しないページが来た。
378          */
379         @Override
380         public void pageType(PageType type) throws HtmlParseException{
381             if(   type != PageType.VILLAGELIST_PAGE
382                && type != PageType.TOP_PAGE ){
383                 throw new HtmlParseException(
384                         "トップページか村一覧ページが必要です。");
385             }
386             return;
387         }
388
389         /**
390          * {@inheritDoc}
391          * @param content {@inheritDoc}
392          * @param anchorRange {@inheritDoc}
393          * @param villageRange {@inheritDoc}
394          * @param hour {@inheritDoc}
395          * @param minute {@inheritDoc}
396          * @param villageState {@inheritDoc}
397          * @throws HtmlParseException {@inheritDoc}
398          */
399         @Override
400         public void villageRecord(DecodedContent content,
401                                     SeqRange anchorRange,
402                                     SeqRange villageRange,
403                                     int hour, int minute,
404                                     VillageState villageState)
405                 throws HtmlParseException{
406             LandDef landdef = getLandDef();
407             LandState landState = landdef.getLandState();
408
409             CharSequence href = anchorRange.sliceSequence(content);
410             String villageID = getVillageIDFromHREF(href);
411             if(   villageID == null
412                || villageID.length() <= 0 ){
413                 LOGGER.warning(
414                         "認識できないURL[" + href + "]に遭遇しました。");
415                  return;
416             }
417
418             CharSequence fullVillageName =
419                     villageRange.sliceSequence(content);
420
421             // TODO 既に出来ているかもしれないVillageを再度作るのは無駄?
422             Village village = new Village(Land.this,
423                                           villageID,
424                                           fullVillageName.toString() );
425
426             if(landState == LandState.HISTORICAL){
427                 village.setState(VillageState.GAMEOVER);
428             }else{
429                 village.setState(villageState);
430             }
431
432             this.villageList.add(village);
433
434             return;
435         }
436
437     }
438
439 }