--- /dev/null
+/*\r
+ * land\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: Land.java 993 2010-03-14 11:55:46Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf;\r
+\r
+import java.awt.image.BufferedImage;\r
+import java.io.IOException;\r
+import java.net.MalformedURLException;\r
+import java.net.URI;\r
+import java.net.URISyntaxException;\r
+import java.net.URL;\r
+import java.util.Collections;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.SortedSet;\r
+import java.util.TreeSet;\r
+import jp.sourceforge.jindolf.corelib.LandDef;\r
+import jp.sourceforge.jindolf.corelib.LandState;\r
+import jp.sourceforge.jindolf.corelib.VillageState;\r
+import jp.sourceforge.jindolf.parser.DecodedContent;\r
+import jp.sourceforge.jindolf.parser.HtmlAdapter;\r
+import jp.sourceforge.jindolf.parser.HtmlParseException;\r
+import jp.sourceforge.jindolf.parser.HtmlParser;\r
+import jp.sourceforge.jindolf.parser.PageType;\r
+import jp.sourceforge.jindolf.parser.SeqRange;\r
+\r
+/**\r
+ * いわゆる「国」。\r
+ */\r
+public class Land {\r
+\r
+ // 古国ID\r
+ private static final String ID_VANILLAWOLF = "wolf";\r
+\r
+ /**\r
+ * クエリー文字列から特定キーの値を得る。\r
+ * クエリーの書式例:「a=b&c=d&e=f」この場合キーcの値はd\r
+ * @param key キー\r
+ * @param allQuery クエリー\r
+ * @return 値\r
+ */\r
+ public static String getValueFromCGIQueries(String key,\r
+ String allQuery){\r
+ String result = null;\r
+\r
+ String[] queries = allQuery.split("\\Q&\\E");\r
+\r
+ for(String pair : queries){\r
+ if(pair == null) continue;\r
+ String[] namevalue = pair.split("\\Q=\\E");\r
+ if(namevalue == null) continue;\r
+ if(namevalue.length != 2) continue;\r
+ String name = namevalue[0];\r
+ String value = namevalue[1];\r
+ if(name == null) continue;\r
+ if( name.equals(key) ){\r
+ result = value;\r
+ if(result == null) continue;\r
+ if(result.length() <= 0) continue;\r
+ break;\r
+ }\r
+ }\r
+\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * AタグのHREF属性値からクエリー部を抽出する。\r
+ * 「{@literal &}」は「{@literal &}」に解釈される。\r
+ * @param hrefValue HREF属性値\r
+ * @return クエリー文字列\r
+ */\r
+ public static String getRawQueryFromHREF(CharSequence hrefValue){\r
+ if(hrefValue == null) return null;\r
+\r
+ // HTML 4.01 B.2.2 rule\r
+ String pureHREF = hrefValue.toString().replace("&", "&");\r
+\r
+ URI uri;\r
+ try{\r
+ uri = new URI(pureHREF);\r
+ }catch(URISyntaxException e){\r
+ Jindolf.logger().warn(\r
+ "不正なURI["\r
+ + hrefValue\r
+ + "]を検出しました");\r
+ return null;\r
+ }\r
+\r
+ String rawQuery = uri.getRawQuery();\r
+\r
+ return rawQuery;\r
+ }\r
+\r
+ /**\r
+ * AタグのHREF属性値から村IDを得る。\r
+ * @param hrefValue HREF値\r
+ * @return village 村ID\r
+ */\r
+ public static String getVillageIDFromHREF(CharSequence hrefValue){\r
+ String rawQuery = getRawQueryFromHREF(hrefValue);\r
+ if(rawQuery == null) return null;\r
+\r
+ String villageID = getValueFromCGIQueries("vid", rawQuery);\r
+ if(villageID == null) return null;\r
+ if(villageID.length() <= 0) return null;\r
+\r
+ return villageID;\r
+ }\r
+\r
+ private final LandDef landDef;\r
+ private final ServerAccess serverAccess;\r
+ private final HtmlParser parser = new HtmlParser();\r
+ private final VillageListHandler handler = new VillageListHandler();\r
+\r
+ private final List<Village> villageList = new LinkedList<Village>();\r
+\r
+ /**\r
+ * コンストラクタ。\r
+ * @param landDef 国定義\r
+ * @throws java.lang.IllegalArgumentException 不正な国定義\r
+ */\r
+ public Land(LandDef landDef) throws IllegalArgumentException{\r
+ super();\r
+\r
+ this.landDef = landDef;\r
+\r
+ URL url;\r
+ try{\r
+ url = this.landDef.getCgiURI().toURL();\r
+ }catch(MalformedURLException e){\r
+ throw new IllegalArgumentException(e);\r
+ }\r
+ this.serverAccess = new ServerAccess(url, this.landDef.getEncoding());\r
+\r
+ this.parser.setBasicHandler(this.handler);\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * 国定義を得る。\r
+ * @return 国定義\r
+ */\r
+ public LandDef getLandDef(){\r
+ return this.landDef;\r
+ }\r
+\r
+ /**\r
+ * サーバ接続を返す。\r
+ * @return ServerAccessインスタンス\r
+ */\r
+ public ServerAccess getServerAccess(){\r
+ return this.serverAccess;\r
+ }\r
+\r
+ /**\r
+ * 指定されたインデックス位置の村を返す。\r
+ * @param index 0から始まるインデックス値\r
+ * @return 村\r
+ */\r
+ public Village getVillage(int index){\r
+ if(index < 0) return null;\r
+ if(index >= getVillageCount()) return null;\r
+\r
+ Village result = this.villageList.get(index);\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * 村の総数を返す。\r
+ * @return 村の総数\r
+ */\r
+ public int getVillageCount(){\r
+ int result = this.villageList.size();\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * 村のリストを返す。\r
+ * @return 村のリスト\r
+ */\r
+ // TODO インスタンス変数でいいはず。\r
+ public List<Village> getVillageList(){\r
+ return Collections.unmodifiableList(this.villageList);\r
+ }\r
+\r
+ /**\r
+ * 絶対または相対URLの指すパーマネントなイメージ画像をダウンロードする。\r
+ * ※ A,B,D 国の顔アイコンは絶対パスらしい…。\r
+ * @param imageURL 画像URL文字列\r
+ * @return 画像イメージ\r
+ */\r
+ public BufferedImage downloadImage(String imageURL){\r
+ ServerAccess server = getServerAccess();\r
+ BufferedImage image;\r
+ try{\r
+ image = server.downloadImage(imageURL);\r
+ }catch(IOException e){\r
+ Jindolf.logger().warn(\r
+ "イメージ[" + imageURL + "]"\r
+ + "のダウンロードに失敗しました",\r
+ e );\r
+ return null;\r
+ }\r
+ return image;\r
+ }\r
+\r
+ /**\r
+ * 墓アイコンイメージを取得する。\r
+ * @return 墓アイコンイメージ\r
+ */\r
+ public BufferedImage getGraveIconImage(){\r
+ URI uri = getLandDef().getTombFaceIconURI();\r
+ BufferedImage result = downloadImage(uri.toASCIIString());\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * 墓アイコンイメージ(大)を取得する。\r
+ * @return 墓アイコンイメージ(大)\r
+ */\r
+ public BufferedImage getGraveBodyImage(){\r
+ URI uri = getLandDef().getTombBodyIconURI();\r
+ BufferedImage result = downloadImage(uri.toASCIIString());\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * 村リストを更新する。\r
+ * 元情報は国のトップページと村一覧ページ。\r
+ * 古国の場合は村一覧にアクセスせずトップページのみ。\r
+ * 古国以外に村建てをやめた国はトップページにアクセスしない。\r
+ * 村リストはVillageの実装に従いソートされる。重複する村は排除。\r
+ * @throws java.io.IOException ネットワーク入出力の異常\r
+ */\r
+ public void updateVillageList() throws IOException{\r
+ LandDef thisLand = getLandDef();\r
+ LandState state = thisLand.getLandState();\r
+ boolean isVanillaWolf = thisLand.getLandId().equals(ID_VANILLAWOLF);\r
+\r
+ ServerAccess server = getServerAccess();\r
+\r
+ // たまに同じ村が複数回出現するので注意!\r
+ SortedSet<Village> vset = new TreeSet<Village>();\r
+\r
+ // トップページ\r
+ if(state.equals(LandState.ACTIVE) || isVanillaWolf){\r
+ HtmlSequence html = server.getHTMLTopPage();\r
+ DecodedContent content = html.getContent();\r
+ try{\r
+ this.parser.parseAutomatic(content);\r
+ }catch(HtmlParseException e){\r
+ Jindolf.logger().warn("トップページを認識できない", e);\r
+ }\r
+ List<Village> list = this.handler.getVillageList();\r
+ if(list != null){\r
+ vset.addAll(list);\r
+ }\r
+ }\r
+\r
+ // 村一覧ページ\r
+ if( ! isVanillaWolf ){\r
+ HtmlSequence html = server.getHTMLLandList();\r
+ DecodedContent content = html.getContent();\r
+ try{\r
+ this.parser.parseAutomatic(content);\r
+ }catch(HtmlParseException e){\r
+ Jindolf.logger().warn("村一覧ページを認識できない", e);\r
+ }\r
+ List<Village> list = this.handler.getVillageList();\r
+ if(list != null){\r
+ vset.addAll(list);\r
+ }\r
+ }\r
+\r
+ // TODO 村リスト更新のイベントリスナがあると便利か?\r
+ this.villageList.clear();\r
+ this.villageList.addAll(vset);\r
+\r
+ this.parser.reset();\r
+ this.handler.reset();\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * 国の文字列表現を返す。\r
+ * @return 文字列表現\r
+ */\r
+ @Override\r
+ public String toString(){\r
+ return getLandDef().getLandName();\r
+ }\r
+\r
+ /**\r
+ * 村一覧取得用ハンドラ。\r
+ */\r
+ private class VillageListHandler extends HtmlAdapter{\r
+\r
+ private List<Village> villageList = null;\r
+\r
+ /**\r
+ * コンストラクタ。\r
+ */\r
+ public VillageListHandler(){\r
+ super();\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * 村一覧を返す。\r
+ * 再度パースを行うまで呼んではいけない。\r
+ * @return 村一覧\r
+ * @throws IllegalStateException パース前に呼び出された。\r
+ * あるいはパース後すでにリセットされている。\r
+ */\r
+ public List<Village> getVillageList() throws IllegalStateException{\r
+ if(this.villageList == null){\r
+ throw new IllegalStateException("パースが必要です。");\r
+ }\r
+\r
+ List<Village> result = this.villageList;\r
+\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * リセットを行う。\r
+ * 村一覧は空になる。\r
+ */\r
+ public void reset(){\r
+ this.villageList = null;\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * {@inheritDoc}\r
+ * 村一覧リストが初期化される。\r
+ * @param content {@inheritDoc}\r
+ * @throws HtmlParseException {@inheritDoc}\r
+ */\r
+ @Override\r
+ public void startParse(DecodedContent content)\r
+ throws HtmlParseException{\r
+ reset();\r
+ this.villageList = new LinkedList<Village>();\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * {@inheritDoc}\r
+ * 自動判定の結果がトップページでも村一覧ページでもなければ\r
+ * 例外を投げる。\r
+ * @param type {@inheritDoc}\r
+ * @throws HtmlParseException {@inheritDoc} 意図しないページが来た。\r
+ */\r
+ @Override\r
+ public void pageType(PageType type) throws HtmlParseException{\r
+ if( type != PageType.VILLAGELIST_PAGE\r
+ && type != PageType.TOP_PAGE ){\r
+ throw new HtmlParseException(\r
+ "トップページか村一覧ページが必要です。");\r
+ }\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * {@inheritDoc}\r
+ * @param content {@inheritDoc}\r
+ * @param anchorRange {@inheritDoc}\r
+ * @param villageRange {@inheritDoc}\r
+ * @param hour {@inheritDoc}\r
+ * @param minute {@inheritDoc}\r
+ * @param villageState {@inheritDoc}\r
+ * @throws HtmlParseException {@inheritDoc}\r
+ */\r
+ @Override\r
+ public void villageRecord(DecodedContent content,\r
+ SeqRange anchorRange,\r
+ SeqRange villageRange,\r
+ int hour, int minute,\r
+ VillageState villageState)\r
+ throws HtmlParseException{\r
+ LandDef landdef = getLandDef();\r
+ LandState landState = landdef.getLandState();\r
+\r
+ CharSequence href = anchorRange.sliceSequence(content);\r
+ String villageID = getVillageIDFromHREF(href);\r
+ if( villageID == null\r
+ || villageID.length() <= 0 ){\r
+ Jindolf.logger().warn(\r
+ "認識できないURL[" + href + "]に遭遇しました。");\r
+ return;\r
+ }\r
+\r
+ CharSequence fullVillageName =\r
+ villageRange.sliceSequence(content);\r
+\r
+ // TODO 既に出来ているかもしれないVillageを再度作るのは無駄?\r
+ Village village = new Village(Land.this,\r
+ villageID,\r
+ fullVillageName.toString() );\r
+\r
+ if(landState == LandState.HISTORICAL){\r
+ village.setState(VillageState.GAMEOVER);\r
+ }else{\r
+ village.setState(villageState);\r
+ }\r
+\r
+ this.villageList.add(village);\r
+\r
+ return;\r
+ }\r
+\r
+ }\r
+\r
+}\r