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 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;
41 private static final String ID_VANILLAWOLF = "wolf";
43 private static final Logger LOGGER = Logger.getAnonymousLogger();
46 private final LandDef landDef;
47 private final ServerAccess serverAccess;
48 private final HtmlParser parser = new HtmlParser();
49 private final VillageListHandler handler = new VillageListHandler();
51 private final List<Village> villageList = new LinkedList<Village>();
57 * @throws java.lang.IllegalArgumentException 不正な国定義
59 public Land(LandDef landDef) throws IllegalArgumentException{
62 this.landDef = landDef;
66 url = this.landDef.getCgiURI().toURL();
67 }catch(MalformedURLException e){
68 throw new IllegalArgumentException(e);
70 this.serverAccess = new ServerAccess(url, this.landDef.getEncoding());
72 this.parser.setBasicHandler(this.handler);
80 * クエリーの書式例:「a=b&c=d&e=f」この場合キーcの値はd
82 * @param allQuery クエリー
85 public static String getValueFromCGIQueries(String key,
89 String[] queries = allQuery.split("\\Q&\\E");
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) ){
101 if(result == null) continue;
102 if(result.length() <= 0) continue;
111 * AタグのHREF属性値からクエリー部を抽出する。
112 * 「{@literal &}」は「{@literal &}」に解釈される。
113 * @param hrefValue HREF属性値
116 public static String getRawQueryFromHREF(CharSequence hrefValue){
117 if(hrefValue == null) return null;
119 // HTML 4.01 B.2.2 rule
120 String pureHREF = hrefValue.toString().replace("&", "&");
124 uri = new URI(pureHREF);
125 }catch(URISyntaxException e){
133 String rawQuery = uri.getRawQuery();
139 * AタグのHREF属性値から村IDを得る。
140 * @param hrefValue HREF値
141 * @return village 村ID
143 public static String getVillageIDFromHREF(CharSequence hrefValue){
144 String rawQuery = getRawQueryFromHREF(hrefValue);
145 if(rawQuery == null) return null;
147 String villageID = getValueFromCGIQueries("vid", rawQuery);
148 if(villageID == null) return null;
149 if(villageID.length() <= 0) return null;
158 public LandDef getLandDef(){
164 * @return ServerAccessインスタンス
166 public ServerAccess getServerAccess(){
167 return this.serverAccess;
171 * 指定されたインデックス位置の村を返す。
172 * @param index 0から始まるインデックス値
175 public Village getVillage(int index){
176 if(index < 0) return null;
177 if(index >= getVillageCount()) return null;
179 Village result = this.villageList.get(index);
187 public int getVillageCount(){
188 int result = this.villageList.size();
196 // TODO インスタンス変数でいいはず。
197 public List<Village> getVillageList(){
198 return Collections.unmodifiableList(this.villageList);
202 * 絶対または相対URLの指すパーマネントなイメージ画像をダウンロードする。
203 * ※ A,B,D 国の顔アイコンは絶対パスらしい…。
204 * @param imageURL 画像URL文字列
207 public BufferedImage downloadImage(String imageURL){
208 ServerAccess server = getServerAccess();
211 image = server.downloadImage(imageURL);
212 }catch(IOException e){
213 LOGGER.log(Level.WARNING,
214 "イメージ[" + imageURL + "]"
226 public BufferedImage getGraveIconImage(){
227 URI uri = getLandDef().getTombFaceIconURI();
228 BufferedImage result = downloadImage(uri.toASCIIString());
234 * @return 墓アイコンイメージ(大)
236 public BufferedImage getGraveBodyImage(){
237 URI uri = getLandDef().getTombBodyIconURI();
238 BufferedImage result = downloadImage(uri.toASCIIString());
244 * リスト元情報は国のトップページと村一覧ページ。
245 * 古国の場合は村一覧にアクセスせずトップページのみ。
246 * 古国以外で村建てをやめた国はトップページにアクセスしない。
247 * 村リストはVillageの実装に従いソートされる。重複する村は排除。
248 * @throws java.io.IOException ネットワーク入出力の異常
251 public SortedSet<Village> downloadVillageList() throws IOException {
252 LandDef thisLand = getLandDef();
253 LandState state = thisLand.getLandState();
254 boolean isVanillaWolf = thisLand.getLandId().equals(ID_VANILLAWOLF);
256 ServerAccess server = getServerAccess();
258 // たまに同じ村が複数回出現するので注意!
259 SortedSet<Village> result = new TreeSet<Village>();
262 if(state.equals(LandState.ACTIVE) || isVanillaWolf){
263 HtmlSequence html = server.getHTMLTopPage();
264 DecodedContent content = html.getContent();
266 this.parser.parseAutomatic(content);
267 }catch(HtmlParseException e){
268 LOGGER.log(Level.WARNING, "トップページを認識できない", e);
270 List<Village> list = this.handler.getVillageList();
277 if( ! isVanillaWolf ){
278 HtmlSequence html = server.getHTMLLandList();
279 DecodedContent content = html.getContent();
281 this.parser.parseAutomatic(content);
282 }catch(HtmlParseException e){
283 LOGGER.log(Level.WARNING, "村一覧ページを認識できない", e);
285 List<Village> list = this.handler.getVillageList();
292 this.handler.reset();
299 * @param vset ソート済みの村一覧
301 public void updateVillageList(SortedSet<Village> vset){
302 // TODO 村リスト更新のイベントリスナがあると便利か?
303 this.villageList.clear();
304 this.villageList.addAll(vset);
313 public String toString(){
314 return getLandDef().getLandName();
320 private class VillageListHandler extends HtmlAdapter{
322 private List<Village> villageList = null;
327 public VillageListHandler(){
334 * 再度パースを行うまで呼んではいけない。
336 * @throws IllegalStateException パース前に呼び出された。
337 * あるいはパース後すでにリセットされている。
339 public List<Village> getVillageList() throws IllegalStateException{
340 if(this.villageList == null){
341 throw new IllegalStateException("パースが必要です。");
344 List<Village> result = this.villageList;
354 this.villageList = null;
361 * @param content {@inheritDoc}
362 * @throws HtmlParseException {@inheritDoc}
365 public void startParse(DecodedContent content)
366 throws HtmlParseException{
368 this.villageList = new LinkedList<Village>();
374 * 自動判定の結果がトップページでも村一覧ページでもなければ
376 * @param type {@inheritDoc}
377 * @throws HtmlParseException {@inheritDoc} 意図しないページが来た。
380 public void pageType(PageType type) throws HtmlParseException{
381 if( type != PageType.VILLAGELIST_PAGE
382 && type != PageType.TOP_PAGE ){
383 throw new HtmlParseException(
384 "トップページか村一覧ページが必要です。");
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}
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();
409 CharSequence href = anchorRange.sliceSequence(content);
410 String villageID = getVillageIDFromHREF(href);
411 if( villageID == null
412 || villageID.length() <= 0 ){
414 "認識できないURL[" + href + "]に遭遇しました。");
418 CharSequence fullVillageName =
419 villageRange.sliceSequence(content);
421 // TODO 既に出来ているかもしれないVillageを再度作るのは無駄?
422 Village village = new Village(Land.this,
424 fullVillageName.toString() );
426 if(landState == LandState.HISTORICAL){
427 village.setState(VillageState.GAMEOVER);
429 village.setState(villageState);
432 this.villageList.add(village);