OSDN Git Service

Merge commit '2458eff3aea04f67893bc824b5cf896fbb767332'
[jindolf/Jindolf.git] / src / main / java / jp / sourceforge / jindolf / Land.java
diff --git a/src/main/java/jp/sourceforge/jindolf/Land.java b/src/main/java/jp/sourceforge/jindolf/Land.java
new file mode 100644 (file)
index 0000000..07756c5
--- /dev/null
@@ -0,0 +1,422 @@
+/*\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 &amp;}」は「{@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("&amp;", "&");\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