OSDN Git Service

Merge branch 'Branch_release-'
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / data / Period.java
-/*\r
- * daily period in village\r
- *\r
- * Copyright(c) 2008 olyutorskii\r
- * $Id: Period.java 1028 2010-05-13 10:15:11Z olyutorskii $\r
- */\r
-\r
-package jp.sourceforge.jindolf;\r
-\r
-import java.io.IOException;\r
-import java.util.Collections;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.LinkedList;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-import jp.sourceforge.jindolf.corelib.EventFamily;\r
-import jp.sourceforge.jindolf.corelib.GameRole;\r
-import jp.sourceforge.jindolf.corelib.LandDef;\r
-import jp.sourceforge.jindolf.corelib.PeriodType;\r
-import jp.sourceforge.jindolf.corelib.SysEventType;\r
-import jp.sourceforge.jindolf.corelib.TalkType;\r
-import jp.sourceforge.jindolf.corelib.Team;\r
-import jp.sourceforge.jindolf.corelib.VillageState;\r
-import jp.sourceforge.jindolf.parser.DecodedContent;\r
-import jp.sourceforge.jindolf.parser.EntityConverter;\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
- *\r
- * 将来、24時間更新でなくなる可能性の考慮が必要。\r
- * 人気のないプロローグなどで、\r
- * 24時間以上の期間を持つPeriodが生成される可能性の考慮が必要。\r
- */\r
-public class Period{\r
-    // TODO Comparable も implement する?\r
-\r
-    private static final HtmlParser PARSER = new HtmlParser();\r
-    private static final PeriodHandler HANDLER =\r
-            new PeriodHandler();\r
-\r
-    static{\r
-        PARSER.setBasicHandler   (HANDLER);\r
-        PARSER.setSysEventHandler(HANDLER);\r
-        PARSER.setTalkHandler    (HANDLER);\r
-    }\r
-\r
-    /**\r
-     * Periodを更新する。Topicのリストが更新される。\r
-     * @param period 日\r
-     * @param force trueなら強制再読み込み。\r
-     * falseならまだ読み込んで無い時のみ読み込み。\r
-     * @throws IOException ネットワーク入力エラー\r
-     */\r
-    public static void parsePeriod(Period period, boolean force)\r
-            throws IOException{\r
-        if( ! force && period.hasLoaded() ) return;\r
-\r
-        Village village = period.getVillage();\r
-        Land land = village.getParentLand();\r
-        ServerAccess server = land.getServerAccess();\r
-\r
-        if(village.getState() != VillageState.PROGRESS){\r
-            period.isFullOpen = true;\r
-        }else if(period.getType() != PeriodType.PROGRESS){\r
-            period.isFullOpen = true;\r
-        }else{\r
-            period.isFullOpen = false;\r
-        }\r
-\r
-        HtmlSequence html = server.getHTMLPeriod(period);\r
-\r
-        period.topicList.clear();\r
-\r
-        boolean wasHot = period.isHot();\r
-\r
-        HANDLER.setPeriod(period);\r
-        DecodedContent content = html.getContent();\r
-        try{\r
-            PARSER.parseAutomatic(content);\r
-        }catch(HtmlParseException e){\r
-            Jindolf.logger().warn("発言抽出に失敗", e);\r
-        }\r
-\r
-        if(wasHot && ! period.isHot() ){\r
-            parsePeriod(period, true);\r
-            return;\r
-        }\r
-\r
-        return;\r
-    }\r
-\r
-    private final Village homeVillage;\r
-    private final PeriodType periodType;\r
-    private final int day;\r
-    private int limitHour;\r
-    private int limitMinute;\r
-    // TODO 更新月日も入れるべきか。\r
-    private String loginName;\r
-    private boolean isFullOpen = false;\r
-\r
-    private final List<Topic> topicList = new LinkedList<Topic>();\r
-    private final List<Topic> unmodList =\r
-            Collections.unmodifiableList(this.topicList);\r
-\r
-    /**\r
-     * この Period が進行中の村の最新日で、\r
-     * 今まさに次々と発言が蓄積されているときは\r
-     * true になる。\r
-     * ※重要: Hot な Period は meslog クエリーを使ってダウンロードできない。\r
-     */\r
-    private boolean isHot;\r
-\r
-    /**\r
-     * Periodを生成する。\r
-     * この段階では発言データのロードは行われない。\r
-     * デフォルトで非Hot状態。\r
-     * @param homeVillage 所属するVillage\r
-     * @param periodType Period種別\r
-     * @param day Period通番\r
-     * @throws java.lang.NullPointerException 引数にnullが渡された場合。\r
-     */\r
-    public Period(Village homeVillage,\r
-                   PeriodType periodType,\r
-                   int day)\r
-                   throws NullPointerException{\r
-        this(homeVillage, periodType, day, false);\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * Periodを生成する。\r
-     * この段階では発言データのロードは行われない。\r
-     * @param homeVillage 所属するVillage\r
-     * @param periodType Period種別\r
-     * @param day Period通番\r
-     * @param isHot Hotか否か\r
-     * @throws java.lang.NullPointerException 引数にnullが渡された場合。\r
-     */\r
-    private Period(Village homeVillage,\r
-                    PeriodType periodType,\r
-                    int day,\r
-                    boolean isHot)\r
-                    throws NullPointerException{\r
-        if(   homeVillage == null\r
-           || periodType  == null ) throw new NullPointerException();\r
-        if(day < 0){\r
-            throw new IllegalArgumentException("Period day is too small !");\r
-        }\r
-        switch(periodType){\r
-        case PROLOGUE:\r
-            assert day == 0;\r
-            break;\r
-        case PROGRESS:\r
-        case EPILOGUE:\r
-            assert day > 0;\r
-            break;\r
-        default:\r
-            assert false;\r
-            break;\r
-        }\r
-\r
-        this.homeVillage = homeVillage;\r
-        this.periodType  = periodType;\r
-        this.day         = day;\r
-\r
-        unload();\r
-\r
-        this.isHot = isHot;\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 所属する村を返す。\r
-     * @return 村\r
-     */\r
-    public Village getVillage(){\r
-        return this.homeVillage;\r
-    }\r
-\r
-    /**\r
-     * Period種別を返す。\r
-     * @return 種別\r
-     */\r
-    public PeriodType getType(){\r
-        return this.periodType;\r
-    }\r
-\r
-    /**\r
-     * Period通番を返す。\r
-     * プロローグは常に0番。\r
-     * n日目のゲーム進行日はn番\r
-     * エピローグは最後のゲーム進行日+1番\r
-     * @return Period通番\r
-     */\r
-    public int getDay(){\r
-        return this.day;\r
-    }\r
-\r
-    /**\r
-     * 更新時刻の文字表記を返す。\r
-     * @return 更新時刻の文字表記\r
-     */\r
-    public String getLimit(){\r
-        StringBuilder result = new StringBuilder();\r
-\r
-        if(this.limitHour < 10) result.append('0');\r
-        result.append(this.limitHour).append(':');\r
-\r
-        if(this.limitMinute < 10) result.append('0');\r
-        result.append(this.limitMinute);\r
-\r
-        return result.toString();\r
-    }\r
-\r
-    /**\r
-     * Hotか否か返す。\r
-     * @return Hotか否か\r
-     */\r
-    public boolean isHot(){\r
-        return this.isHot;\r
-    }\r
-\r
-    /**\r
-     * Hotか否か設定する。\r
-     * @param isHotArg Hot指定\r
-     */\r
-    public void setHot(boolean isHotArg){\r
-        this.isHot = isHotArg;\r
-    }\r
-\r
-    /**\r
-     * プロローグか否か判定する。\r
-     * @return プロローグならtrue\r
-     */\r
-    public boolean isPrologue(){\r
-        if(getType() == PeriodType.PROLOGUE) return true;\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * エピローグか否か判定する。\r
-     * @return エピローグならtrue\r
-     */\r
-    public boolean isEpilogue(){\r
-        if(getType() == PeriodType.EPILOGUE) return true;\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * 進行日か否か判定する。\r
-     * @return 進行日ならtrue\r
-     */\r
-    public boolean isProgress(){\r
-        if(getType() == PeriodType.PROGRESS) return true;\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * このPeriodにアクセスするためのクエリーを生成する。\r
-     * @return CGIに渡すクエリー\r
-     */\r
-    public String getCGIQuery(){\r
-        StringBuilder result = new StringBuilder();\r
-\r
-        Village village = getVillage();\r
-        result.append(village.getCGIQuery());\r
-\r
-        if(isHot()){\r
-            result.append("&mes=all");   // 全表示指定\r
-            return result.toString();\r
-        }\r
-\r
-        Land land = village.getParentLand();\r
-        LandDef ldef = land.getLandDef();\r
-\r
-        if(ldef.getLandId().equals("wolfg")){\r
-            result.append("&meslog=");\r
-            String dnum = "000" + (getDay() - 1);\r
-            dnum = dnum.substring(dnum.length() - 3);\r
-            switch(getType()){\r
-            case PROLOGUE:\r
-                result.append("000_ready");\r
-                break;\r
-            case PROGRESS:\r
-                result.append(dnum).append("_progress");\r
-                break;\r
-            case EPILOGUE:\r
-                result.append(dnum).append("_party");\r
-                break;\r
-            default:\r
-                assert false;\r
-                return null;\r
-            }\r
-        }else{\r
-            result.append("&meslog=").append(village.getVillageID());\r
-            switch(getType()){\r
-            case PROLOGUE:\r
-                result.append("_ready_0");\r
-                break;\r
-            case PROGRESS:\r
-                result.append("_progress_").append(getDay() - 1);\r
-                break;\r
-            case EPILOGUE:\r
-                result.append("_party_").append(getDay() - 1);\r
-                break;\r
-            default:\r
-                assert false;\r
-                return null;\r
-            }\r
-        }\r
-\r
-\r
-        result.append("&mes=all");\r
-\r
-        return result.toString();\r
-    }\r
-\r
-    /**\r
-     * Periodに含まれるTopicのリストを返す。\r
-     * このリストは上書き操作不能。\r
-     * @return Topicのリスト\r
-     */\r
-    public List<Topic> getTopicList(){\r
-        return this.unmodList;\r
-    }\r
-\r
-    /**\r
-     * Periodに含まれるTopicの総数を返す。\r
-     * @return Topic総数\r
-     */\r
-    public int getTopics(){\r
-        return this.topicList.size();\r
-    }\r
-\r
-    /**\r
-     * Topicを追加する。\r
-     * @param topic Topic\r
-     * @throws java.lang.NullPointerException nullが渡された場合。\r
-     */\r
-    protected void addTopic(Topic topic) throws NullPointerException{\r
-        if(topic == null) throw new NullPointerException();\r
-        this.topicList.add(topic);\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * Periodのキャプション文字列を返す。\r
-     * 主な用途はタブ画面の耳のラベルなど。\r
-     * @return キャプション文字列\r
-     */\r
-    public String getCaption(){\r
-        String result;\r
-\r
-        switch(getType()){\r
-        case PROLOGUE:\r
-            result = "プロローグ";\r
-            break;\r
-        case PROGRESS:\r
-            result = getDay() + "日目";\r
-            break;\r
-        case EPILOGUE:\r
-            result = "エピローグ";\r
-            break;\r
-        default:\r
-            assert false;\r
-            result = null;\r
-            break;\r
-        }\r
-\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     * このPeriodをダウンロードしたときのログイン名を返す。\r
-     * @return ログイン名。ログアウト中はnull。\r
-     */\r
-    public String getLoginName(){\r
-        return this.loginName;\r
-    }\r
-\r
-    /**\r
-     * 公開発言番号にマッチする発言を返す。\r
-     * @param talkNo 公開発言番号\r
-     * @return 発言。見つからなければnull\r
-     */\r
-    public Talk getNumberedTalk(int talkNo){\r
-        if(talkNo <= 0) throw new IllegalArgumentException();\r
-\r
-        for(Topic topic : this.topicList){\r
-            if( ! (topic instanceof Talk) ) continue;\r
-            Talk talk = (Talk) topic;\r
-            if(talkNo == talk.getTalkNo()) return talk;\r
-        }\r
-\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * このPeriodの内容にゲーム進行上隠された部分がある可能性を判定する。\r
-     * @return 隠れた要素がありうるならfalse\r
-     */\r
-    public boolean isFullOpen(){\r
-        return this.isFullOpen;\r
-    }\r
-\r
-    /**\r
-     * ロード済みか否かチェックする。\r
-     * @return ロード済みならtrue\r
-     */\r
-    public boolean hasLoaded(){\r
-        return getTopics() > 0;\r
-    }\r
-\r
-    /**\r
-     * 発言データをアンロードする。\r
-     */\r
-    public void unload(){\r
-        this.limitHour = 0;\r
-        this.limitMinute = 0;\r
-        this.loginName = null;\r
-        this.isFullOpen = false;\r
-\r
-        this.isHot = false;\r
-\r
-        this.topicList.clear();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 襲撃メッセージの有無を判定する。\r
-     * 決着が付くまで非狼陣営には見えない。\r
-     * 偽装GJでは狼にも見えない。\r
-     * @return 襲撃メッセージがあればtrue\r
-     */\r
-    public boolean hasAssaultTried(){\r
-        for(Topic topic : this.topicList){\r
-            if(topic instanceof Talk){\r
-                Talk talk = (Talk) topic;\r
-                if(talk.getTalkCount() <= 0) return true;\r
-            }else if(topic instanceof SysEvent){\r
-                SysEvent sysEvent = (SysEvent) topic;\r
-                SysEventType type = sysEvent.getSysEventType();\r
-                if(type == SysEventType.ASSAULT) return true;\r
-            }\r
-        }\r
-\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * 処刑されたAvatarを返す。\r
-     * @return 処刑されたAvatar。突然死などなんらかの理由でいない場合はnull\r
-     */\r
-    public Avatar getExecutedAvatar(){\r
-        Avatar result = null;\r
-\r
-        for(Topic topic : getTopicList()){\r
-            if( ! (topic instanceof SysEvent) ) continue;\r
-            SysEvent event = (SysEvent) topic;\r
-            result = event.getExecutedAvatar();\r
-            if(result != null) break;\r
-        }\r
-\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     * 投票に参加したAvatarの集合を返す。\r
-     * @return 投票に参加したAvatarのSet\r
-     */\r
-    public Set<Avatar> getVoterSet(){\r
-        Set<Avatar> result = new HashSet<Avatar>();\r
-\r
-        for(Topic topic : getTopicList()){\r
-            if( ! (topic instanceof SysEvent) ) continue;\r
-            SysEvent event = (SysEvent) topic;\r
-            result = event.getVoterSet(result);\r
-        }\r
-\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     * 任意のタイプのシステムイベントを返す。\r
-     * 複数存在する場合、返すのは最初の一つだけ。\r
-     * @param type イベントタイプ\r
-     * @return システムイベント\r
-     */\r
-    public SysEvent getTypedSysEvent(SysEventType type){\r
-        for(Topic topic : getTopicList()){\r
-            if( ! (topic instanceof SysEvent) ) continue;\r
-            SysEvent event = (SysEvent) topic;\r
-            if(event.getSysEventType() == type) return event;\r
-        }\r
-\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * Periodパース用ハンドラ。\r
-     */\r
-    private static class PeriodHandler extends HtmlAdapter{\r
-\r
-        private static final int TALKTYPE_NUM = TalkType.values().length;\r
-\r
-        private final EntityConverter converter =\r
-                new EntityConverter();\r
-\r
-        private final Map<Avatar, int[]> countMap =\r
-                new HashMap<Avatar, int[]>();\r
-\r
-        private Period period = null;\r
-\r
-        private TalkType talkType;\r
-        private Avatar avatar;\r
-        private int talkNo;\r
-        private String anchorId;\r
-        private int talkHour;\r
-        private int talkMinute;\r
-        private DecodedContent talkContent = null;\r
-\r
-        private EventFamily eventFamily;\r
-        private SysEventType sysEventType;\r
-        private DecodedContent eventContent = null;\r
-        private final List<Avatar> avatarList = new LinkedList<Avatar>();\r
-        private final List<GameRole> roleList = new LinkedList<GameRole>();\r
-        private final List<Integer> integerList = new LinkedList<Integer>();\r
-        private final List<CharSequence>  charseqList =\r
-            new LinkedList<CharSequence>();\r
-\r
-        /**\r
-         * コンストラクタ。\r
-         */\r
-        public PeriodHandler(){\r
-            super();\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * パース結果を格納するPeriodを設定する。\r
-         * @param period Period\r
-         */\r
-        public void setPeriod(Period period){\r
-            this.period = period;\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * 文字列断片からAvatarを得る。\r
-         * 村に未登録のAvatarであればついでに登録される。\r
-         * @param content 文字列\r
-         * @param range 文字列内のAvatarフルネームを示す領域\r
-         * @return Avatar\r
-         */\r
-        private Avatar toAvatar(DecodedContent content, SeqRange range){\r
-            Village village = this.period.getVillage();\r
-            String fullName = this.converter\r
-                                  .convert(content, range)\r
-                                  .toString();\r
-            Avatar result = village.getAvatar(fullName);\r
-            if(result == null){\r
-                result = new Avatar(fullName);\r
-                village.addAvatar(result);\r
-            }\r
-\r
-            return result;\r
-        }\r
-\r
-        /**\r
-         * Avatar別、会話種ごとに発言回数をカウントする。\r
-         * 1から始まる。\r
-         * @param targetAvatar 対象Avatar\r
-         * @param targetType 対象会話種\r
-         * @return カウント数\r
-         */\r
-        private int countUp(Avatar targetAvatar, TalkType targetType){\r
-            int[] countArray = this.countMap.get(targetAvatar);\r
-            if(countArray == null){\r
-                countArray = new int[TALKTYPE_NUM];\r
-                this.countMap.put(targetAvatar, countArray);\r
-            }\r
-            int count = ++countArray[targetType.ordinal()];\r
-            return count;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void startParse(DecodedContent content)\r
-                throws HtmlParseException{\r
-            this.period.loginName = null;\r
-            this.period.topicList.clear();\r
-            this.countMap.clear();\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param loginRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void loginName(DecodedContent content, SeqRange loginRange)\r
-                throws HtmlParseException{\r
-            DecodedContent loginName =\r
-                    this.converter.convert(content, loginRange);\r
-\r
-            this.period.loginName = loginName.toString();\r
-\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param type {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void pageType(PageType type) throws HtmlParseException{\r
-            if(type != PageType.PERIOD_PAGE){\r
-                throw new HtmlParseException(\r
-                        "意図しないページを読み込もうとしました。");\r
-            }\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param month {@inheritDoc}\r
-         * @param day {@inheritDoc}\r
-         * @param hour {@inheritDoc}\r
-         * @param minute {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void commitTime(int month, int day, int hour, int minute)\r
-                throws HtmlParseException{\r
-            this.period.limitHour   = hour;\r
-            this.period.limitMinute = minute;\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * 自分へのリンクが無いかチェックする。\r
-         * 自分へのリンクが見つかればこのPeriodを非Hotにする。\r
-         * 自分へのリンクがあるということは、\r
-         * 今読んでるHTMLは別のPeriodのために書かれたものということ。\r
-         * 考えられる原因は、HotだったPeriodがゲーム進行に従い\r
-         * Hotでなくなったこと。\r
-         * @param content {@inheritDoc}\r
-         * @param anchorRange {@inheritDoc}\r
-         * @param periodType {@inheritDoc}\r
-         * @param day {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void periodLink(DecodedContent content,\r
-                                SeqRange anchorRange,\r
-                                PeriodType periodType,\r
-                                int day)\r
-                throws HtmlParseException{\r
-\r
-            if(this.period.getType() != periodType) return;\r
-\r
-            if(   periodType == PeriodType.PROGRESS\r
-               && this.period.getDay() != day ){\r
-                return;\r
-            }\r
-\r
-            if( ! anchorRange.isValid() ) return;\r
-\r
-            this.period.setHot(false);\r
-\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void startTalk() throws HtmlParseException{\r
-            this.talkType = null;\r
-            this.avatar = null;\r
-            this.talkNo = -1;\r
-            this.anchorId = null;\r
-            this.talkHour = -1;\r
-            this.talkMinute = -1;\r
-            this.talkContent = new DecodedContent(100 + 1);\r
-\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param type {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void talkType(TalkType type)\r
-                throws HtmlParseException{\r
-            this.talkType = type;\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param avatarRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void talkAvatar(DecodedContent content, SeqRange avatarRange)\r
-                throws HtmlParseException{\r
-            this.avatar = toAvatar(content, avatarRange);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param hour {@inheritDoc}\r
-         * @param minute {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void talkTime(int hour, int minute)\r
-                throws HtmlParseException{\r
-            this.talkHour = hour;\r
-            this.talkMinute = minute;\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param tno {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void talkNo(int tno) throws HtmlParseException{\r
-            this.talkNo = tno;\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param idRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void talkId(DecodedContent content, SeqRange idRange)\r
-                throws HtmlParseException{\r
-            this.anchorId = content.subSequence(idRange.getStartPos(),\r
-                                                idRange.getEndPos()   )\r
-                                   .toString();\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param textRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void talkText(DecodedContent content, SeqRange textRange)\r
-                throws HtmlParseException{\r
-            this.converter.append(this.talkContent, content, textRange);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void talkBreak()\r
-                throws HtmlParseException{\r
-            this.talkContent.append('\n');\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void endTalk() throws HtmlParseException{\r
-            Talk talk = new Talk(this.period,\r
-                                 this.talkType,\r
-                                 this.avatar,\r
-                                 this.talkNo,\r
-                                 this.anchorId,\r
-                                 this.talkHour, this.talkMinute,\r
-                                 this.talkContent );\r
-\r
-            int count = countUp(this.avatar, this.talkType);\r
-            talk.setCount(count);\r
-\r
-            this.period.addTopic(talk);\r
-\r
-            this.talkType = null;\r
-            this.avatar = null;\r
-            this.talkNo = -1;\r
-            this.anchorId = null;\r
-            this.talkHour = -1;\r
-            this.talkMinute = -1;\r
-            this.talkContent = null;\r
-\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param family {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void startSysEvent(EventFamily family)\r
-                throws HtmlParseException{\r
-            this.eventFamily = family;\r
-            this.sysEventType = null;\r
-            this.eventContent = new DecodedContent();\r
-            this.avatarList.clear();\r
-            this.roleList.clear();\r
-            this.integerList.clear();\r
-            this.charseqList.clear();\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param type {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventType(SysEventType type)\r
-                throws HtmlParseException{\r
-            this.sysEventType = type;\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param contentRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventContent(DecodedContent content,\r
-                                      SeqRange contentRange)\r
-                throws HtmlParseException{\r
-            this.converter.append(this.eventContent, content, contentRange);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param anchorRange {@inheritDoc}\r
-         * @param contentRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventContentAnchor(DecodedContent content,\r
-                                             SeqRange anchorRange,\r
-                                             SeqRange contentRange)\r
-                throws HtmlParseException{\r
-            this.converter.append(this.eventContent, content, contentRange);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventContentBreak() throws HtmlParseException{\r
-            this.eventContent.append('\n');\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param entryNo {@inheritDoc}\r
-         * @param avatarRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventOnStage(DecodedContent content,\r
-                                      int entryNo,\r
-                                      SeqRange avatarRange)\r
-                throws HtmlParseException{\r
-            Avatar newAvatar = toAvatar(content, avatarRange);\r
-            this.integerList.add(entryNo);\r
-            this.avatarList.add(newAvatar);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param role {@inheritDoc}\r
-         * @param num {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventOpenRole(GameRole role, int num)\r
-                throws HtmlParseException{\r
-            this.roleList.add(role);\r
-            this.integerList.add(num);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param avatarRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventMurdered(DecodedContent content,\r
-                                       SeqRange avatarRange)\r
-                throws HtmlParseException{\r
-            Avatar murdered = toAvatar(content, avatarRange);\r
-            this.avatarList.add(murdered);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param avatarRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventSurvivor(DecodedContent content,\r
-                                       SeqRange avatarRange)\r
-                throws HtmlParseException{\r
-            Avatar survivor = toAvatar(content, avatarRange);\r
-            this.avatarList.add(survivor);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param voteByRange {@inheritDoc}\r
-         * @param voteToRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventCounting(DecodedContent content,\r
-                                       SeqRange voteByRange,\r
-                                       SeqRange voteToRange)\r
-                throws HtmlParseException{\r
-            if(voteByRange.isValid()){\r
-                Avatar voteBy = toAvatar(content, voteByRange);\r
-                this.avatarList.add(voteBy);\r
-            }\r
-            Avatar voteTo = toAvatar(content, voteToRange);\r
-            this.avatarList.add(voteTo);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param voteByRange {@inheritDoc}\r
-         * @param voteToRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventCounting2(DecodedContent content,\r
-                                        SeqRange voteByRange,\r
-                                        SeqRange voteToRange)\r
-                throws HtmlParseException{\r
-            sysEventCounting(content, voteByRange, voteToRange);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param avatarRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventSuddenDeath(DecodedContent content,\r
-                                           SeqRange avatarRange)\r
-                throws HtmlParseException{\r
-            Avatar suddenDeath = toAvatar(content, avatarRange);\r
-            this.avatarList.add(suddenDeath);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param avatarRange {@inheritDoc}\r
-         * @param anchorRange {@inheritDoc}\r
-         * @param loginRange {@inheritDoc}\r
-         * @param isLiving {@inheritDoc}\r
-         * @param role {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventPlayerList(DecodedContent content,\r
-                                          SeqRange avatarRange,\r
-                                          SeqRange anchorRange,\r
-                                          SeqRange loginRange,\r
-                                          boolean isLiving,\r
-                                          GameRole role )\r
-                throws HtmlParseException{\r
-            Avatar who = toAvatar(content, avatarRange);\r
-\r
-            CharSequence anchor;\r
-            if(anchorRange.isValid()){\r
-                anchor = this.converter.convert(content, anchorRange);\r
-            }else{\r
-                anchor = "";\r
-            }\r
-            CharSequence account = this.converter\r
-                                       .convert(content, loginRange);\r
-\r
-            Integer liveOrDead;\r
-            if(isLiving) liveOrDead = Integer.valueOf(1);\r
-            else         liveOrDead = Integer.valueOf(0);\r
-\r
-            this.avatarList.add(who);\r
-            this.charseqList.add(anchor);\r
-            this.charseqList.add(account);\r
-            this.integerList.add(liveOrDead);\r
-            this.roleList.add(role);\r
-\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param avatarRange {@inheritDoc}\r
-         * @param votes {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventExecution(DecodedContent content,\r
-                                        SeqRange avatarRange,\r
-                                        int votes )\r
-                throws HtmlParseException{\r
-            Avatar who = toAvatar(content, avatarRange);\r
-\r
-            this.avatarList.add(who);\r
-            this.integerList.add(votes);\r
-\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param hour {@inheritDoc}\r
-         * @param minute {@inheritDoc}\r
-         * @param minLimit {@inheritDoc}\r
-         * @param maxLimit {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventAskEntry(int hour, int minute,\r
-                                       int minLimit, int maxLimit)\r
-                throws HtmlParseException{\r
-            this.integerList.add(hour * 60 + minute);\r
-            this.integerList.add(minLimit);\r
-            this.integerList.add(maxLimit);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param hour {@inheritDoc}\r
-         * @param minute {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventAskCommit(int hour, int minute)\r
-                throws HtmlParseException{\r
-            this.integerList.add(hour * 60 + minute);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param avatarRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventNoComment(DecodedContent content,\r
-                                        SeqRange avatarRange)\r
-                throws HtmlParseException{\r
-            Avatar noComAvatar = toAvatar(content, avatarRange);\r
-            this.avatarList.add(noComAvatar);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param winner {@inheritDoc}\r
-         * @param hour {@inheritDoc}\r
-         * @param minute {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventStayEpilogue(Team winner, int hour, int minute)\r
-                throws HtmlParseException{\r
-            GameRole role = null;\r
-\r
-            switch(winner){\r
-            case VILLAGE: role = GameRole.INNOCENT; break;\r
-            case WOLF:    role = GameRole.WOLF;     break;\r
-            case HAMSTER: role = GameRole.HAMSTER;  break;\r
-            default: assert false; break;\r
-            }\r
-\r
-            this.roleList.add(role);\r
-            this.integerList.add(hour * 60 + minute);\r
-\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param guardByRange {@inheritDoc}\r
-         * @param guardToRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventGuard(DecodedContent content,\r
-                                    SeqRange guardByRange,\r
-                                    SeqRange guardToRange)\r
-                throws HtmlParseException{\r
-            Avatar guardBy = toAvatar(content, guardByRange);\r
-            Avatar guardTo = toAvatar(content, guardToRange);\r
-            this.avatarList.add(guardBy);\r
-            this.avatarList.add(guardTo);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @param content {@inheritDoc}\r
-         * @param judgeByRange {@inheritDoc}\r
-         * @param judgeToRange {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void sysEventJudge(DecodedContent content,\r
-                                    SeqRange judgeByRange,\r
-                                    SeqRange judgeToRange)\r
-                throws HtmlParseException{\r
-            Avatar judgeBy = toAvatar(content, judgeByRange);\r
-            Avatar judgeTo = toAvatar(content, judgeToRange);\r
-            this.avatarList.add(judgeBy);\r
-            this.avatarList.add(judgeTo);\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void endSysEvent() throws HtmlParseException{\r
-            SysEvent event = new SysEvent();\r
-            event.setEventFamily(this.eventFamily);\r
-            event.setSysEventType(this.sysEventType);\r
-            event.setContent(this.eventContent);\r
-            event.addAvatarList(this.avatarList);\r
-            event.addRoleList(this.roleList);\r
-            event.addIntegerList(this.integerList);\r
-            event.addCharSequenceList(this.charseqList);\r
-\r
-            this.period.addTopic(event);\r
-\r
-            if(   this.sysEventType == SysEventType.MURDERED\r
-               || this.sysEventType == SysEventType.NOMURDER ){\r
-                for(Topic topic : this.period.topicList){\r
-                    if( ! (topic instanceof Talk) ) continue;\r
-                    Talk talk = (Talk) topic;\r
-                    if(talk.getTalkType() != TalkType.WOLFONLY) continue;\r
-                    if( ! StringUtils\r
-                         .isTerminated(talk.getDialog(),\r
-                                       "!\u0020今日がお前の命日だ!") ){\r
-                        continue;\r
-                    }\r
-                    talk.setCount(-1);\r
-                    this.countMap.clear();\r
-                }\r
-            }\r
-\r
-            this.eventFamily = null;\r
-            this.sysEventType = null;\r
-            this.eventContent = null;\r
-            this.avatarList.clear();\r
-            this.roleList.clear();\r
-            this.integerList.clear();\r
-            this.charseqList.clear();\r
-\r
-            return;\r
-        }\r
-\r
-        /**\r
-         * {@inheritDoc}\r
-         * @throws HtmlParseException {@inheritDoc}\r
-         */\r
-        @Override\r
-        public void endParse() throws HtmlParseException{\r
-            return;\r
-        }\r
-\r
-        // TODO 村名のチェックは不要か?\r
-    }\r
-\r
-}\r
+/*
+ * daily period in village
+ *
+ * License : The MIT License
+ * Copyright(c) 2008 olyutorskii
+ */
+
+package jp.sfjp.jindolf.data;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import jp.osdn.jindolf.parser.EntityConverter;
+import jp.osdn.jindolf.parser.HtmlAdapter;
+import jp.osdn.jindolf.parser.HtmlParseException;
+import jp.osdn.jindolf.parser.HtmlParser;
+import jp.osdn.jindolf.parser.PageType;
+import jp.osdn.jindolf.parser.SeqRange;
+import jp.osdn.jindolf.parser.content.DecodedContent;
+import jp.sfjp.jindolf.net.HtmlSequence;
+import jp.sfjp.jindolf.net.ServerAccess;
+import jp.sfjp.jindolf.util.StringUtils;
+import jp.sourceforge.jindolf.corelib.EventFamily;
+import jp.sourceforge.jindolf.corelib.GameRole;
+import jp.sourceforge.jindolf.corelib.LandDef;
+import jp.sourceforge.jindolf.corelib.PeriodType;
+import jp.sourceforge.jindolf.corelib.SysEventType;
+import jp.sourceforge.jindolf.corelib.TalkType;
+import jp.sourceforge.jindolf.corelib.Team;
+import jp.sourceforge.jindolf.corelib.VillageState;
+
+/**
+ * いわゆる「日」。
+ * 村の進行の一区切り。プロローグやエピローグも含まれる。
+ *
+ * <p>将来、24時間更新でなくなる可能性の考慮が必要。
+ * 人気のないプロローグなどで、
+ * 24時間以上の期間を持つPeriodが生成される可能性の考慮が必要。
+ */
+public class Period{
+    // TODO Comparable も implement する?
+
+    private static final HtmlParser PARSER = new HtmlParser();
+    private static final PeriodHandler HANDLER =
+            new PeriodHandler();
+
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
+
+    static{
+        PARSER.setBasicHandler   (HANDLER);
+        PARSER.setSysEventHandler(HANDLER);
+        PARSER.setTalkHandler    (HANDLER);
+    }
+
+    private final Village homeVillage;
+    private final PeriodType periodType;
+    private final int day;
+    private int limitHour;
+    private int limitMinute;
+    // TODO 更新月日も入れるべきか。
+    private String loginName;
+    private boolean isFullOpen = false;
+
+    private final List<Topic> topicList = new LinkedList<>();
+    private final List<Topic> unmodList =
+            Collections.unmodifiableList(this.topicList);
+
+
+    /**
+     * この Period が進行中の村の最新日で、
+     * 今まさに次々と発言が蓄積されているときは
+     * true になる。
+     * ※重要: Hot な Period は meslog クエリーを使ってダウンロードできない。
+     */
+    private boolean isHot;
+
+
+    /**
+     * Periodを生成する。
+     * この段階では発言データのロードは行われない。
+     * デフォルトで非Hot状態。
+     * @param homeVillage 所属するVillage
+     * @param periodType Period種別
+     * @param day Period通番
+     * @throws java.lang.NullPointerException 引数にnullが渡された場合。
+     */
+    public Period(Village homeVillage,
+                   PeriodType periodType,
+                   int day)
+                   throws NullPointerException{
+        this(homeVillage, periodType, day, false);
+        return;
+    }
+
+    /**
+     * Periodを生成する。
+     * この段階では発言データのロードは行われない。
+     * @param homeVillage 所属するVillage
+     * @param periodType Period種別
+     * @param day Period通番
+     * @param isHot Hotか否か
+     * @throws java.lang.NullPointerException 引数にnullが渡された場合。
+     */
+    private Period(Village homeVillage,
+                    PeriodType periodType,
+                    int day,
+                    boolean isHot)
+                    throws NullPointerException{
+        if(    homeVillage == null
+            || periodType  == null ) throw new NullPointerException();
+        if(day < 0){
+            throw new IllegalArgumentException("Period day is too small !");
+        }
+        switch(periodType){
+        case PROLOGUE:
+            assert day == 0;
+            break;
+        case PROGRESS:
+        case EPILOGUE:
+            assert day > 0;
+            break;
+        default:
+            assert false;
+            break;
+        }
+
+        this.homeVillage = homeVillage;
+        this.periodType  = periodType;
+        this.day         = day;
+
+        unload();
+
+        this.isHot = isHot;
+
+        return;
+    }
+
+
+    /**
+     * Periodを更新する。Topicのリストが更新される。
+     * @param period 日
+     * @param force trueなら強制再読み込み。
+     *     falseならまだ読み込んで無い時のみ読み込み。
+     * @throws IOException ネットワーク入力エラー
+     */
+    public static void parsePeriod(Period period, boolean force)
+            throws IOException{
+        if( ! force && period.hasLoaded() ) return;
+
+        Village village = period.getVillage();
+        Land land = village.getParentLand();
+        ServerAccess server = land.getServerAccess();
+
+        if(village.getState() != VillageState.PROGRESS){
+            period.isFullOpen = true;
+        }else if(period.getType() != PeriodType.PROGRESS){
+            period.isFullOpen = true;
+        }else{
+            period.isFullOpen = false;
+        }
+
+        HtmlSequence html = server.getHTMLPeriod(period);
+
+        period.topicList.clear();
+
+        boolean wasHot = period.isHot();
+
+        HANDLER.setPeriod(period);
+        DecodedContent content = html.getContent();
+        try{
+            PARSER.parseAutomatic(content);
+        }catch(HtmlParseException e){
+            LOGGER.log(Level.WARNING, "発言抽出に失敗", e);
+        }
+
+        if(wasHot && ! period.isHot() ){
+            parsePeriod(period, true);
+            return;
+        }
+
+        return;
+    }
+
+    /**
+     * 所属する村を返す。
+     * @return 村
+     */
+    public Village getVillage(){
+        return this.homeVillage;
+    }
+
+    /**
+     * Period種別を返す。
+     * @return 種別
+     */
+    public PeriodType getType(){
+        return this.periodType;
+    }
+
+    /**
+     * Period通番を返す。
+     * プロローグは常に0番。
+     * n日目のゲーム進行日はn番
+     * エピローグは最後のゲーム進行日+1番
+     * @return Period通番
+     */
+    public int getDay(){
+        return this.day;
+    }
+
+    /**
+     * 更新時刻の文字表記を返す。
+     * @return 更新時刻の文字表記
+     */
+    public String getLimit(){
+        StringBuilder result = new StringBuilder();
+
+        if(this.limitHour < 10) result.append('0');
+        result.append(this.limitHour).append(':');
+
+        if(this.limitMinute < 10) result.append('0');
+        result.append(this.limitMinute);
+
+        return result.toString();
+    }
+
+    /**
+     * Hotか否か返す。
+     * @return Hotか否か
+     */
+    public boolean isHot(){
+        return this.isHot;
+    }
+
+    /**
+     * Hotか否か設定する。
+     * @param isHotArg Hot指定
+     */
+    public void setHot(boolean isHotArg){
+        this.isHot = isHotArg;
+    }
+
+    /**
+     * プロローグか否か判定する。
+     * @return プロローグならtrue
+     */
+    public boolean isPrologue(){
+        if(getType() == PeriodType.PROLOGUE) return true;
+        return false;
+    }
+
+    /**
+     * エピローグか否か判定する。
+     * @return エピローグならtrue
+     */
+    public boolean isEpilogue(){
+        if(getType() == PeriodType.EPILOGUE) return true;
+        return false;
+    }
+
+    /**
+     * 進行日か否か判定する。
+     * @return 進行日ならtrue
+     */
+    public boolean isProgress(){
+        if(getType() == PeriodType.PROGRESS) return true;
+        return false;
+    }
+
+    /**
+     * このPeriodにアクセスするためのクエリーを生成する。
+     * @return CGIに渡すクエリー
+     */
+    public String getCGIQuery(){
+        StringBuilder result = new StringBuilder();
+
+        Village village = getVillage();
+        result.append(village.getCGIQuery());
+
+        if(isHot()){
+            result.append("&mes=all");   // 全表示指定
+            return result.toString();
+        }
+
+        Land land = village.getParentLand();
+        LandDef ldef = land.getLandDef();
+
+        if(ldef.getLandId().equals("wolfg")){
+            result.append("&meslog=");
+            String dnum = "000" + (getDay() - 1);
+            dnum = dnum.substring(dnum.length() - 3);
+            switch(getType()){
+            case PROLOGUE:
+                result.append("000_ready");
+                break;
+            case PROGRESS:
+                result.append(dnum).append("_progress");
+                break;
+            case EPILOGUE:
+                result.append(dnum).append("_party");
+                break;
+            default:
+                assert false;
+                return null;
+            }
+        }else{
+            result.append("&meslog=").append(village.getVillageID());
+            switch(getType()){
+            case PROLOGUE:
+                result.append("_ready_0");
+                break;
+            case PROGRESS:
+                result.append("_progress_").append(getDay() - 1);
+                break;
+            case EPILOGUE:
+                result.append("_party_").append(getDay() - 1);
+                break;
+            default:
+                assert false;
+                return null;
+            }
+        }
+
+
+        result.append("&mes=all");
+
+        return result.toString();
+    }
+
+    /**
+     * Periodに含まれるTopicのリストを返す。
+     * このリストは上書き操作不能。
+     * @return Topicのリスト
+     */
+    public List<Topic> getTopicList(){
+        return this.unmodList;
+    }
+
+    /**
+     * Periodに含まれるTopicの総数を返す。
+     * @return Topic総数
+     */
+    public int getTopics(){
+        return this.topicList.size();
+    }
+
+    /**
+     * Topicを追加する。
+     * @param topic Topic
+     * @throws java.lang.NullPointerException nullが渡された場合。
+     */
+    protected void addTopic(Topic topic) throws NullPointerException{
+        if(topic == null) throw new NullPointerException();
+        this.topicList.add(topic);
+        return;
+    }
+
+    /**
+     * Periodのキャプション文字列を返す。
+     * 主な用途はタブ画面の耳のラベルなど。
+     * @return キャプション文字列
+     */
+    public String getCaption(){
+        String result;
+
+        switch(getType()){
+        case PROLOGUE:
+            result = "プロローグ";
+            break;
+        case PROGRESS:
+            result = getDay() + "日目";
+            break;
+        case EPILOGUE:
+            result = "エピローグ";
+            break;
+        default:
+            assert false;
+            result = null;
+            break;
+        }
+
+        return result;
+    }
+
+    /**
+     * このPeriodをダウンロードしたときのログイン名を返す。
+     * @return ログイン名。ログアウト中はnull。
+     */
+    public String getLoginName(){
+        return this.loginName;
+    }
+
+    /**
+     * 公開発言番号にマッチする発言を返す。
+     * @param talkNo 公開発言番号
+     * @return 発言。見つからなければnull
+     */
+    public Talk getNumberedTalk(int talkNo){
+        if(talkNo <= 0) throw new IllegalArgumentException();
+
+        for(Topic topic : this.topicList){
+            if( ! (topic instanceof Talk) ) continue;
+            Talk talk = (Talk) topic;
+            if(talkNo == talk.getTalkNo()) return talk;
+        }
+
+        return null;
+    }
+
+    /**
+     * このPeriodの内容にゲーム進行上隠された部分がある可能性を判定する。
+     * @return 隠れた要素がありうるならfalse
+     */
+    public boolean isFullOpen(){
+        return this.isFullOpen;
+    }
+
+    /**
+     * ロード済みか否かチェックする。
+     * @return ロード済みならtrue
+     */
+    public boolean hasLoaded(){
+        return getTopics() > 0;
+    }
+
+    /**
+     * 発言データをアンロードする。
+     */
+    public void unload(){
+        this.limitHour = 0;
+        this.limitMinute = 0;
+        this.loginName = null;
+        this.isFullOpen = false;
+
+        this.isHot = false;
+
+        this.topicList.clear();
+
+        return;
+    }
+
+    /**
+     * 襲撃メッセージの有無を判定する。
+     * 決着が付くまで非狼陣営には見えない。
+     * 偽装GJでは狼にも見えない。
+     * @return 襲撃メッセージがあればtrue
+     */
+    public boolean hasAssaultTried(){
+        for(Topic topic : this.topicList){
+            if(topic instanceof Talk){
+                Talk talk = (Talk) topic;
+                if(talk.getTalkCount() <= 0) return true;
+            }else if(topic instanceof SysEvent){
+                SysEvent sysEvent = (SysEvent) topic;
+                SysEventType type = sysEvent.getSysEventType();
+                if(type == SysEventType.ASSAULT) return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 処刑されたAvatarを返す。
+     * @return 処刑されたAvatar。突然死などなんらかの理由でいない場合はnull
+     */
+    public Avatar getExecutedAvatar(){
+        Avatar result = null;
+
+        for(Topic topic : getTopicList()){
+            if( ! (topic instanceof SysEvent) ) continue;
+            SysEvent event = (SysEvent) topic;
+            result = event.getExecutedAvatar();
+            if(result != null) break;
+        }
+
+        return result;
+    }
+
+    /**
+     * 投票に参加したAvatarの集合を返す。
+     * @return 投票に参加したAvatarのSet
+     */
+    public Set<Avatar> getVoterSet(){
+        Set<Avatar> result = new HashSet<>();
+
+        for(Topic topic : getTopicList()){
+            if( ! (topic instanceof SysEvent) ) continue;
+            SysEvent event = (SysEvent) topic;
+            result = event.getVoterSet(result);
+        }
+
+        return result;
+    }
+
+    /**
+     * 任意のタイプのシステムイベントを返す。
+     * 複数存在する場合、返すのは最初の一つだけ。
+     * @param type イベントタイプ
+     * @return システムイベント
+     */
+    public SysEvent getTypedSysEvent(SysEventType type){
+        for(Topic topic : getTopicList()){
+            if( ! (topic instanceof SysEvent) ) continue;
+            SysEvent event = (SysEvent) topic;
+            if(event.getSysEventType() == type) return event;
+        }
+
+        return null;
+    }
+
+    /**
+     * Periodパース用ハンドラ。
+     */
+    private static class PeriodHandler extends HtmlAdapter{
+
+        private static final int TALKTYPE_NUM = TalkType.values().length;
+
+        private final EntityConverter converter =
+                new EntityConverter(true);
+        // TODO: SMP面文字に彩色対応するまでの暫定措置
+
+        private final Map<Avatar, int[]> countMap =
+                new HashMap<>();
+
+        private Period period = null;
+
+        private TalkType talkType;
+        private Avatar avatar;
+        private int talkNo;
+        private String anchorId;
+        private int talkHour;
+        private int talkMinute;
+        private DecodedContent talkContent = null;
+
+        private EventFamily eventFamily;
+        private SysEventType sysEventType;
+        private DecodedContent eventContent = null;
+        private final List<Avatar> avatarList = new LinkedList<>();
+        private final List<GameRole> roleList = new LinkedList<>();
+        private final List<Integer> integerList = new LinkedList<>();
+        private final List<CharSequence>  charseqList =
+            new LinkedList<>();
+
+        /**
+         * コンストラクタ。
+         */
+        public PeriodHandler(){
+            super();
+            return;
+        }
+
+        /**
+         * パース結果を格納するPeriodを設定する。
+         * @param period Period
+         */
+        public void setPeriod(Period period){
+            this.period = period;
+            return;
+        }
+
+        /**
+         * 文字列断片からAvatarを得る。
+         * 村に未登録のAvatarであればついでに登録される。
+         * @param content 文字列
+         * @param range 文字列内のAvatarフルネームを示す領域
+         * @return Avatar
+         */
+        private Avatar toAvatar(DecodedContent content, SeqRange range){
+            Village village = this.period.getVillage();
+            String fullName = this.converter
+                                  .convert(content, range)
+                                  .toString();
+            Avatar result = village.getAvatar(fullName);
+            if(result == null){
+                result = new Avatar(fullName);
+                village.addAvatar(result);
+            }
+
+            return result;
+        }
+
+        /**
+         * Avatar別、会話種ごとに発言回数をカウントする。
+         * 1から始まる。
+         * @param targetAvatar 対象Avatar
+         * @param targetType 対象会話種
+         * @return カウント数
+         */
+        private int countUp(Avatar targetAvatar, TalkType targetType){
+            int[] countArray = this.countMap.get(targetAvatar);
+            if(countArray == null){
+                countArray = new int[TALKTYPE_NUM];
+                this.countMap.put(targetAvatar, countArray);
+            }
+            int count = ++countArray[targetType.ordinal()];
+            return count;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void startParse(DecodedContent content)
+                throws HtmlParseException{
+            this.period.loginName = null;
+            this.period.topicList.clear();
+            this.countMap.clear();
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param loginRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void loginName(DecodedContent content, SeqRange loginRange)
+                throws HtmlParseException{
+            DecodedContent loginName =
+                    this.converter.convert(content, loginRange);
+
+            this.period.loginName = loginName.toString();
+
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param type {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void pageType(PageType type) throws HtmlParseException{
+            if(type != PageType.PERIOD_PAGE){
+                throw new HtmlParseException(
+                        "意図しないページを読み込もうとしました。");
+            }
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param month {@inheritDoc}
+         * @param day {@inheritDoc}
+         * @param hour {@inheritDoc}
+         * @param minute {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void commitTime(int month, int day, int hour, int minute)
+                throws HtmlParseException{
+            this.period.limitHour   = hour;
+            this.period.limitMinute = minute;
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 自分へのリンクが無いかチェックする。
+         * 自分へのリンクが見つかればこのPeriodを非Hotにする。
+         * 自分へのリンクがあるということは、
+         * 今読んでるHTMLは別のPeriodのために書かれたものということ。
+         * 考えられる原因は、HotだったPeriodがゲーム進行に従い
+         * Hotでなくなったこと。
+         * @param content {@inheritDoc}
+         * @param anchorRange {@inheritDoc}
+         * @param periodType {@inheritDoc}
+         * @param day {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void periodLink(DecodedContent content,
+                                SeqRange anchorRange,
+                                PeriodType periodType,
+                                int day)
+                throws HtmlParseException{
+
+            if(this.period.getType() != periodType) return;
+
+            if(    periodType == PeriodType.PROGRESS
+                && this.period.getDay() != day ){
+                return;
+            }
+
+            if( ! anchorRange.isValid() ) return;
+
+            this.period.setHot(false);
+
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void startTalk() throws HtmlParseException{
+            this.talkType = null;
+            this.avatar = null;
+            this.talkNo = -1;
+            this.anchorId = null;
+            this.talkHour = -1;
+            this.talkMinute = -1;
+            this.talkContent = new DecodedContent(100 + 1);
+
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param type {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void talkType(TalkType type)
+                throws HtmlParseException{
+            this.talkType = type;
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param avatarRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void talkAvatar(DecodedContent content, SeqRange avatarRange)
+                throws HtmlParseException{
+            this.avatar = toAvatar(content, avatarRange);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param hour {@inheritDoc}
+         * @param minute {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void talkTime(int hour, int minute)
+                throws HtmlParseException{
+            this.talkHour = hour;
+            this.talkMinute = minute;
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param tno {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void talkNo(int tno) throws HtmlParseException{
+            this.talkNo = tno;
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param idRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void talkId(DecodedContent content, SeqRange idRange)
+                throws HtmlParseException{
+            this.anchorId = content.subSequence(idRange.getStartPos(),
+                                                idRange.getEndPos()   )
+                                   .toString();
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param textRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void talkText(DecodedContent content, SeqRange textRange)
+                throws HtmlParseException{
+            this.converter.append(this.talkContent, content, textRange);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void talkBreak()
+                throws HtmlParseException{
+            this.talkContent.append('\n');
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void endTalk() throws HtmlParseException{
+            Talk talk = new Talk(this.period,
+                                 this.talkType,
+                                 this.avatar,
+                                 this.talkNo,
+                                 this.anchorId,
+                                 this.talkHour, this.talkMinute,
+                                 this.talkContent );
+
+            int count = countUp(this.avatar, this.talkType);
+            talk.setCount(count);
+
+            this.period.addTopic(talk);
+
+            this.talkType = null;
+            this.avatar = null;
+            this.talkNo = -1;
+            this.anchorId = null;
+            this.talkHour = -1;
+            this.talkMinute = -1;
+            this.talkContent = null;
+
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param family {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void startSysEvent(EventFamily family)
+                throws HtmlParseException{
+            this.eventFamily = family;
+            this.sysEventType = null;
+            this.eventContent = new DecodedContent();
+            this.avatarList.clear();
+            this.roleList.clear();
+            this.integerList.clear();
+            this.charseqList.clear();
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param type {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventType(SysEventType type)
+                throws HtmlParseException{
+            this.sysEventType = type;
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param contentRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventContent(DecodedContent content,
+                                      SeqRange contentRange)
+                throws HtmlParseException{
+            this.converter.append(this.eventContent, content, contentRange);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param anchorRange {@inheritDoc}
+         * @param contentRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventContentAnchor(DecodedContent content,
+                                             SeqRange anchorRange,
+                                             SeqRange contentRange)
+                throws HtmlParseException{
+            this.converter.append(this.eventContent, content, contentRange);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventContentBreak() throws HtmlParseException{
+            this.eventContent.append('\n');
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param entryNo {@inheritDoc}
+         * @param avatarRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventOnStage(DecodedContent content,
+                                      int entryNo,
+                                      SeqRange avatarRange)
+                throws HtmlParseException{
+            Avatar newAvatar = toAvatar(content, avatarRange);
+            this.integerList.add(entryNo);
+            this.avatarList.add(newAvatar);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param role {@inheritDoc}
+         * @param num {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventOpenRole(GameRole role, int num)
+                throws HtmlParseException{
+            this.roleList.add(role);
+            this.integerList.add(num);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param avatarRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventMurdered(DecodedContent content,
+                                       SeqRange avatarRange)
+                throws HtmlParseException{
+            Avatar murdered = toAvatar(content, avatarRange);
+            this.avatarList.add(murdered);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param avatarRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventSurvivor(DecodedContent content,
+                                       SeqRange avatarRange)
+                throws HtmlParseException{
+            Avatar survivor = toAvatar(content, avatarRange);
+            this.avatarList.add(survivor);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param voteByRange {@inheritDoc}
+         * @param voteToRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventCounting(DecodedContent content,
+                                       SeqRange voteByRange,
+                                       SeqRange voteToRange)
+                throws HtmlParseException{
+            if(voteByRange.isValid()){
+                Avatar voteBy = toAvatar(content, voteByRange);
+                this.avatarList.add(voteBy);
+            }
+            Avatar voteTo = toAvatar(content, voteToRange);
+            this.avatarList.add(voteTo);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param voteByRange {@inheritDoc}
+         * @param voteToRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventCounting2(DecodedContent content,
+                                        SeqRange voteByRange,
+                                        SeqRange voteToRange)
+                throws HtmlParseException{
+            sysEventCounting(content, voteByRange, voteToRange);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param avatarRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventSuddenDeath(DecodedContent content,
+                                           SeqRange avatarRange)
+                throws HtmlParseException{
+            Avatar suddenDeath = toAvatar(content, avatarRange);
+            this.avatarList.add(suddenDeath);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param avatarRange {@inheritDoc}
+         * @param anchorRange {@inheritDoc}
+         * @param loginRange {@inheritDoc}
+         * @param isLiving {@inheritDoc}
+         * @param role {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventPlayerList(DecodedContent content,
+                                          SeqRange avatarRange,
+                                          SeqRange anchorRange,
+                                          SeqRange loginRange,
+                                          boolean isLiving,
+                                          GameRole role )
+                throws HtmlParseException{
+            Avatar who = toAvatar(content, avatarRange);
+
+            CharSequence anchor;
+            if(anchorRange.isValid()){
+                anchor = this.converter.convert(content, anchorRange);
+            }else{
+                anchor = "";
+            }
+            CharSequence account = this.converter
+                                       .convert(content, loginRange);
+
+            Integer liveOrDead;
+            if(isLiving) liveOrDead = 1;
+            else         liveOrDead = 0;
+
+            this.avatarList.add(who);
+            this.charseqList.add(anchor);
+            this.charseqList.add(account);
+            this.integerList.add(liveOrDead);
+            this.roleList.add(role);
+
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param avatarRange {@inheritDoc}
+         * @param votes {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventExecution(DecodedContent content,
+                                        SeqRange avatarRange,
+                                        int votes )
+                throws HtmlParseException{
+            Avatar who = toAvatar(content, avatarRange);
+
+            this.avatarList.add(who);
+            this.integerList.add(votes);
+
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param hour {@inheritDoc}
+         * @param minute {@inheritDoc}
+         * @param minLimit {@inheritDoc}
+         * @param maxLimit {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventAskEntry(int hour, int minute,
+                                       int minLimit, int maxLimit)
+                throws HtmlParseException{
+            this.integerList.add(hour * 60 + minute);
+            this.integerList.add(minLimit);
+            this.integerList.add(maxLimit);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param hour {@inheritDoc}
+         * @param minute {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventAskCommit(int hour, int minute)
+                throws HtmlParseException{
+            this.integerList.add(hour * 60 + minute);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param avatarRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventNoComment(DecodedContent content,
+                                        SeqRange avatarRange)
+                throws HtmlParseException{
+            Avatar noComAvatar = toAvatar(content, avatarRange);
+            this.avatarList.add(noComAvatar);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param winner {@inheritDoc}
+         * @param hour {@inheritDoc}
+         * @param minute {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventStayEpilogue(Team winner, int hour, int minute)
+                throws HtmlParseException{
+            GameRole role = null;
+
+            switch(winner){
+            case VILLAGE: role = GameRole.INNOCENT; break;
+            case WOLF:    role = GameRole.WOLF;     break;
+            case HAMSTER: role = GameRole.HAMSTER;  break;
+            default: assert false; break;
+            }
+
+            this.roleList.add(role);
+            this.integerList.add(hour * 60 + minute);
+
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param guardByRange {@inheritDoc}
+         * @param guardToRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventGuard(DecodedContent content,
+                                    SeqRange guardByRange,
+                                    SeqRange guardToRange)
+                throws HtmlParseException{
+            Avatar guardBy = toAvatar(content, guardByRange);
+            Avatar guardTo = toAvatar(content, guardToRange);
+            this.avatarList.add(guardBy);
+            this.avatarList.add(guardTo);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @param content {@inheritDoc}
+         * @param judgeByRange {@inheritDoc}
+         * @param judgeToRange {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void sysEventJudge(DecodedContent content,
+                                    SeqRange judgeByRange,
+                                    SeqRange judgeToRange)
+                throws HtmlParseException{
+            Avatar judgeBy = toAvatar(content, judgeByRange);
+            Avatar judgeTo = toAvatar(content, judgeToRange);
+            this.avatarList.add(judgeBy);
+            this.avatarList.add(judgeTo);
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void endSysEvent() throws HtmlParseException{
+            SysEvent event = new SysEvent();
+            event.setEventFamily(this.eventFamily);
+            event.setSysEventType(this.sysEventType);
+            event.setContent(this.eventContent);
+            event.addAvatarList(this.avatarList);
+            event.addRoleList(this.roleList);
+            event.addIntegerList(this.integerList);
+            event.addCharSequenceList(this.charseqList);
+
+            this.period.addTopic(event);
+
+            if(    this.sysEventType == SysEventType.MURDERED
+                || this.sysEventType == SysEventType.NOMURDER ){
+                for(Topic topic : this.period.topicList){
+                    if( ! (topic instanceof Talk) ) continue;
+                    Talk talk = (Talk) topic;
+                    if(talk.getTalkType() != TalkType.WOLFONLY) continue;
+                    if( ! StringUtils
+                         .isTerminated(talk.getDialog(),
+                                       "!\u0020今日がお前の命日だ!") ){
+                        continue;
+                    }
+                    talk.setCount(-1);
+                    this.countMap.clear();
+                }
+            }
+
+            this.eventFamily = null;
+            this.sysEventType = null;
+            this.eventContent = null;
+            this.avatarList.clear();
+            this.roleList.clear();
+            this.integerList.clear();
+            this.charseqList.clear();
+
+            return;
+        }
+
+        /**
+         * {@inheritDoc}
+         * @throws HtmlParseException {@inheritDoc}
+         */
+        @Override
+        public void endParse() throws HtmlParseException{
+            return;
+        }
+
+        // TODO 村名のチェックは不要か?
+    }
+
+}