OSDN Git Service

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