4 * License : The MIT License
5 * Copyright(c) 2008 olyutorskii
8 package jp.sfjp.jindolf.data;
10 import java.awt.image.BufferedImage;
11 import java.io.IOException;
12 import java.net.MalformedURLException;
14 import java.net.URISyntaxException;
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;
40 private static final String ID_VANILLAWOLF = "wolf";
42 private static final LogWrapper LOGGER = new LogWrapper();
45 private final LandDef landDef;
46 private final ServerAccess serverAccess;
47 private final HtmlParser parser = new HtmlParser();
48 private final VillageListHandler handler = new VillageListHandler();
50 private final List<Village> villageList = new LinkedList<Village>();
56 * @throws java.lang.IllegalArgumentException 不正な国定義
58 public Land(LandDef landDef) throws IllegalArgumentException{
61 this.landDef = landDef;
65 url = this.landDef.getCgiURI().toURL();
66 }catch(MalformedURLException e){
67 throw new IllegalArgumentException(e);
69 this.serverAccess = new ServerAccess(url, this.landDef.getEncoding());
71 this.parser.setBasicHandler(this.handler);
79 * クエリーの書式例:「a=b&c=d&e=f」この場合キーcの値はd
81 * @param allQuery クエリー
84 public static String getValueFromCGIQueries(String key,
88 String[] queries = allQuery.split("\\Q&\\E");
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) ){
100 if(result == null) continue;
101 if(result.length() <= 0) continue;
110 * AタグのHREF属性値からクエリー部を抽出する。
111 * 「{@literal &}」は「{@literal &}」に解釈される。
112 * @param hrefValue HREF属性値
115 public static String getRawQueryFromHREF(CharSequence hrefValue){
116 if(hrefValue == null) return null;
118 // HTML 4.01 B.2.2 rule
119 String pureHREF = hrefValue.toString().replace("&", "&");
123 uri = new URI(pureHREF);
124 }catch(URISyntaxException e){
132 String rawQuery = uri.getRawQuery();
138 * AタグのHREF属性値から村IDを得る。
139 * @param hrefValue HREF値
140 * @return village 村ID
142 public static String getVillageIDFromHREF(CharSequence hrefValue){
143 String rawQuery = getRawQueryFromHREF(hrefValue);
144 if(rawQuery == null) return null;
146 String villageID = getValueFromCGIQueries("vid", rawQuery);
147 if(villageID == null) return null;
148 if(villageID.length() <= 0) return null;
157 public LandDef getLandDef(){
163 * @return ServerAccessインスタンス
165 public ServerAccess getServerAccess(){
166 return this.serverAccess;
170 * 指定されたインデックス位置の村を返す。
171 * @param index 0から始まるインデックス値
174 public Village getVillage(int index){
175 if(index < 0) return null;
176 if(index >= getVillageCount()) return null;
178 Village result = this.villageList.get(index);
186 public int getVillageCount(){
187 int result = this.villageList.size();
195 // TODO インスタンス変数でいいはず。
196 public List<Village> getVillageList(){
197 return Collections.unmodifiableList(this.villageList);
201 * 絶対または相対URLの指すパーマネントなイメージ画像をダウンロードする。
202 * ※ A,B,D 国の顔アイコンは絶対パスらしい…。
203 * @param imageURL 画像URL文字列
206 public BufferedImage downloadImage(String imageURL){
207 ServerAccess server = getServerAccess();
210 image = server.downloadImage(imageURL);
211 }catch(IOException e){
213 "イメージ[" + imageURL + "]"
225 public BufferedImage getGraveIconImage(){
226 URI uri = getLandDef().getTombFaceIconURI();
227 BufferedImage result = downloadImage(uri.toASCIIString());
233 * @return 墓アイコンイメージ(大)
235 public BufferedImage getGraveBodyImage(){
236 URI uri = getLandDef().getTombBodyIconURI();
237 BufferedImage result = downloadImage(uri.toASCIIString());
243 * 元情報は国のトップページと村一覧ページ。
244 * 古国の場合は村一覧にアクセスせずトップページのみ。
245 * 古国以外に村建てをやめた国はトップページにアクセスしない。
246 * 村リストはVillageの実装に従いソートされる。重複する村は排除。
247 * @throws java.io.IOException ネットワーク入出力の異常
249 public void updateVillageList() throws IOException{
250 LandDef thisLand = getLandDef();
251 LandState state = thisLand.getLandState();
252 boolean isVanillaWolf = thisLand.getLandId().equals(ID_VANILLAWOLF);
254 ServerAccess server = getServerAccess();
256 // たまに同じ村が複数回出現するので注意!
257 SortedSet<Village> vset = new TreeSet<Village>();
260 if(state.equals(LandState.ACTIVE) || isVanillaWolf){
261 HtmlSequence html = server.getHTMLTopPage();
262 DecodedContent content = html.getContent();
264 this.parser.parseAutomatic(content);
265 }catch(HtmlParseException e){
266 LOGGER.warn("トップページを認識できない", e);
268 List<Village> list = this.handler.getVillageList();
275 if( ! isVanillaWolf ){
276 HtmlSequence html = server.getHTMLLandList();
277 DecodedContent content = html.getContent();
279 this.parser.parseAutomatic(content);
280 }catch(HtmlParseException e){
281 LOGGER.warn("村一覧ページを認識できない", e);
283 List<Village> list = this.handler.getVillageList();
289 // TODO 村リスト更新のイベントリスナがあると便利か?
290 this.villageList.clear();
291 this.villageList.addAll(vset);
294 this.handler.reset();
304 public String toString(){
305 return getLandDef().getLandName();
311 private class VillageListHandler extends HtmlAdapter{
313 private List<Village> villageList = null;
318 public VillageListHandler(){
325 * 再度パースを行うまで呼んではいけない。
327 * @throws IllegalStateException パース前に呼び出された。
328 * あるいはパース後すでにリセットされている。
330 public List<Village> getVillageList() throws IllegalStateException{
331 if(this.villageList == null){
332 throw new IllegalStateException("パースが必要です。");
335 List<Village> result = this.villageList;
345 this.villageList = null;
352 * @param content {@inheritDoc}
353 * @throws HtmlParseException {@inheritDoc}
356 public void startParse(DecodedContent content)
357 throws HtmlParseException{
359 this.villageList = new LinkedList<Village>();
365 * 自動判定の結果がトップページでも村一覧ページでもなければ
367 * @param type {@inheritDoc}
368 * @throws HtmlParseException {@inheritDoc} 意図しないページが来た。
371 public void pageType(PageType type) throws HtmlParseException{
372 if( type != PageType.VILLAGELIST_PAGE
373 && type != PageType.TOP_PAGE ){
374 throw new HtmlParseException(
375 "トップページか村一覧ページが必要です。");
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}
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();
400 CharSequence href = anchorRange.sliceSequence(content);
401 String villageID = getVillageIDFromHREF(href);
402 if( villageID == null
403 || villageID.length() <= 0 ){
405 "認識できないURL[" + href + "]に遭遇しました。");
409 CharSequence fullVillageName =
410 villageRange.sliceSequence(content);
412 // TODO 既に出来ているかもしれないVillageを再度作るのは無駄?
413 Village village = new Village(Land.this,
415 fullVillageName.toString() );
417 if(landState == LandState.HISTORICAL){
418 village.setState(VillageState.GAMEOVER);
420 village.setState(villageState);
423 this.villageList.add(village);