OSDN Git Service

スタートアップ処理の改善
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / data / Period.java
1 /*
2  * daily period in village
3  *
4  * License : The MIT License
5  * Copyright(c) 2008 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.data;
9
10 import java.io.IOException;
11 import java.util.Collections;
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.LinkedList;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Set;
18 import java.util.logging.Level;
19 import java.util.logging.Logger;
20 import jp.sfjp.jindolf.net.HtmlSequence;
21 import jp.sfjp.jindolf.net.ServerAccess;
22 import jp.sfjp.jindolf.util.StringUtils;
23 import jp.sourceforge.jindolf.corelib.EventFamily;
24 import jp.sourceforge.jindolf.corelib.GameRole;
25 import jp.sourceforge.jindolf.corelib.LandDef;
26 import jp.sourceforge.jindolf.corelib.PeriodType;
27 import jp.sourceforge.jindolf.corelib.SysEventType;
28 import jp.sourceforge.jindolf.corelib.TalkType;
29 import jp.sourceforge.jindolf.corelib.Team;
30 import jp.sourceforge.jindolf.corelib.VillageState;
31 import jp.sourceforge.jindolf.parser.DecodedContent;
32 import jp.sourceforge.jindolf.parser.EntityConverter;
33 import jp.sourceforge.jindolf.parser.HtmlAdapter;
34 import jp.sourceforge.jindolf.parser.HtmlParseException;
35 import jp.sourceforge.jindolf.parser.HtmlParser;
36 import jp.sourceforge.jindolf.parser.PageType;
37 import jp.sourceforge.jindolf.parser.SeqRange;
38
39 /**
40  * いわゆる「日」。
41  * 村の進行の一区切り。プロローグやエピローグも含まれる。
42  *
43  * 将来、24時間更新でなくなる可能性の考慮が必要。
44  * 人気のないプロローグなどで、
45  * 24時間以上の期間を持つPeriodが生成される可能性の考慮が必要。
46  */
47 public class Period{
48     // TODO Comparable も implement する?
49
50     private static final HtmlParser PARSER = new HtmlParser();
51     private static final PeriodHandler HANDLER =
52             new PeriodHandler();
53
54     private static final Logger LOGGER = Logger.getAnonymousLogger();
55
56     static{
57         PARSER.setBasicHandler   (HANDLER);
58         PARSER.setSysEventHandler(HANDLER);
59         PARSER.setTalkHandler    (HANDLER);
60     }
61
62     private final Village homeVillage;
63     private final PeriodType periodType;
64     private final int day;
65     private int limitHour;
66     private int limitMinute;
67     // TODO 更新月日も入れるべきか。
68     private String loginName;
69     private boolean isFullOpen = false;
70
71     private final List<Topic> topicList = new LinkedList<Topic>();
72     private final List<Topic> unmodList =
73             Collections.unmodifiableList(this.topicList);
74
75
76     /**
77      * この Period が進行中の村の最新日で、
78      * 今まさに次々と発言が蓄積されているときは
79      * true になる。
80      * ※重要: Hot な Period は meslog クエリーを使ってダウンロードできない。
81      */
82     private boolean isHot;
83
84
85     /**
86      * Periodを生成する。
87      * この段階では発言データのロードは行われない。
88      * デフォルトで非Hot状態。
89      * @param homeVillage 所属するVillage
90      * @param periodType Period種別
91      * @param day Period通番
92      * @throws java.lang.NullPointerException 引数にnullが渡された場合。
93      */
94     public Period(Village homeVillage,
95                    PeriodType periodType,
96                    int day)
97                    throws NullPointerException{
98         this(homeVillage, periodType, day, false);
99         return;
100     }
101
102     /**
103      * Periodを生成する。
104      * この段階では発言データのロードは行われない。
105      * @param homeVillage 所属するVillage
106      * @param periodType Period種別
107      * @param day Period通番
108      * @param isHot Hotか否か
109      * @throws java.lang.NullPointerException 引数にnullが渡された場合。
110      */
111     private Period(Village homeVillage,
112                     PeriodType periodType,
113                     int day,
114                     boolean isHot)
115                     throws NullPointerException{
116         if(   homeVillage == null
117            || periodType  == null ) throw new NullPointerException();
118         if(day < 0){
119             throw new IllegalArgumentException("Period day is too small !");
120         }
121         switch(periodType){
122         case PROLOGUE:
123             assert day == 0;
124             break;
125         case PROGRESS:
126         case EPILOGUE:
127             assert day > 0;
128             break;
129         default:
130             assert false;
131             break;
132         }
133
134         this.homeVillage = homeVillage;
135         this.periodType  = periodType;
136         this.day         = day;
137
138         unload();
139
140         this.isHot = isHot;
141
142         return;
143     }
144
145
146     /**
147      * Periodを更新する。Topicのリストが更新される。
148      * @param period 日
149      * @param force trueなら強制再読み込み。
150      * falseならまだ読み込んで無い時のみ読み込み。
151      * @throws IOException ネットワーク入力エラー
152      */
153     public static void parsePeriod(Period period, boolean force)
154             throws IOException{
155         if( ! force && period.hasLoaded() ) return;
156
157         Village village = period.getVillage();
158         Land land = village.getParentLand();
159         ServerAccess server = land.getServerAccess();
160
161         if(village.getState() != VillageState.PROGRESS){
162             period.isFullOpen = true;
163         }else if(period.getType() != PeriodType.PROGRESS){
164             period.isFullOpen = true;
165         }else{
166             period.isFullOpen = false;
167         }
168
169         HtmlSequence html = server.getHTMLPeriod(period);
170
171         period.topicList.clear();
172
173         boolean wasHot = period.isHot();
174
175         HANDLER.setPeriod(period);
176         DecodedContent content = html.getContent();
177         try{
178             PARSER.parseAutomatic(content);
179         }catch(HtmlParseException e){
180             LOGGER.log(Level.WARNING, "発言抽出に失敗", e);
181         }
182
183         if(wasHot && ! period.isHot() ){
184             parsePeriod(period, true);
185             return;
186         }
187
188         return;
189     }
190
191     /**
192      * 所属する村を返す。
193      * @return 村
194      */
195     public Village getVillage(){
196         return this.homeVillage;
197     }
198
199     /**
200      * Period種別を返す。
201      * @return 種別
202      */
203     public PeriodType getType(){
204         return this.periodType;
205     }
206
207     /**
208      * Period通番を返す。
209      * プロローグは常に0番。
210      * n日目のゲーム進行日はn番
211      * エピローグは最後のゲーム進行日+1番
212      * @return Period通番
213      */
214     public int getDay(){
215         return this.day;
216     }
217
218     /**
219      * 更新時刻の文字表記を返す。
220      * @return 更新時刻の文字表記
221      */
222     public String getLimit(){
223         StringBuilder result = new StringBuilder();
224
225         if(this.limitHour < 10) result.append('0');
226         result.append(this.limitHour).append(':');
227
228         if(this.limitMinute < 10) result.append('0');
229         result.append(this.limitMinute);
230
231         return result.toString();
232     }
233
234     /**
235      * Hotか否か返す。
236      * @return Hotか否か
237      */
238     public boolean isHot(){
239         return this.isHot;
240     }
241
242     /**
243      * Hotか否か設定する。
244      * @param isHotArg Hot指定
245      */
246     public void setHot(boolean isHotArg){
247         this.isHot = isHotArg;
248     }
249
250     /**
251      * プロローグか否か判定する。
252      * @return プロローグならtrue
253      */
254     public boolean isPrologue(){
255         if(getType() == PeriodType.PROLOGUE) return true;
256         return false;
257     }
258
259     /**
260      * エピローグか否か判定する。
261      * @return エピローグならtrue
262      */
263     public boolean isEpilogue(){
264         if(getType() == PeriodType.EPILOGUE) return true;
265         return false;
266     }
267
268     /**
269      * 進行日か否か判定する。
270      * @return 進行日ならtrue
271      */
272     public boolean isProgress(){
273         if(getType() == PeriodType.PROGRESS) return true;
274         return false;
275     }
276
277     /**
278      * このPeriodにアクセスするためのクエリーを生成する。
279      * @return CGIに渡すクエリー
280      */
281     public String getCGIQuery(){
282         StringBuilder result = new StringBuilder();
283
284         Village village = getVillage();
285         result.append(village.getCGIQuery());
286
287         if(isHot()){
288             result.append("&mes=all");   // 全表示指定
289             return result.toString();
290         }
291
292         Land land = village.getParentLand();
293         LandDef ldef = land.getLandDef();
294
295         if(ldef.getLandId().equals("wolfg")){
296             result.append("&meslog=");
297             String dnum = "000" + (getDay() - 1);
298             dnum = dnum.substring(dnum.length() - 3);
299             switch(getType()){
300             case PROLOGUE:
301                 result.append("000_ready");
302                 break;
303             case PROGRESS:
304                 result.append(dnum).append("_progress");
305                 break;
306             case EPILOGUE:
307                 result.append(dnum).append("_party");
308                 break;
309             default:
310                 assert false;
311                 return null;
312             }
313         }else{
314             result.append("&meslog=").append(village.getVillageID());
315             switch(getType()){
316             case PROLOGUE:
317                 result.append("_ready_0");
318                 break;
319             case PROGRESS:
320                 result.append("_progress_").append(getDay() - 1);
321                 break;
322             case EPILOGUE:
323                 result.append("_party_").append(getDay() - 1);
324                 break;
325             default:
326                 assert false;
327                 return null;
328             }
329         }
330
331
332         result.append("&mes=all");
333
334         return result.toString();
335     }
336
337     /**
338      * Periodに含まれるTopicのリストを返す。
339      * このリストは上書き操作不能。
340      * @return Topicのリスト
341      */
342     public List<Topic> getTopicList(){
343         return this.unmodList;
344     }
345
346     /**
347      * Periodに含まれるTopicの総数を返す。
348      * @return Topic総数
349      */
350     public int getTopics(){
351         return this.topicList.size();
352     }
353
354     /**
355      * Topicを追加する。
356      * @param topic Topic
357      * @throws java.lang.NullPointerException nullが渡された場合。
358      */
359     protected void addTopic(Topic topic) throws NullPointerException{
360         if(topic == null) throw new NullPointerException();
361         this.topicList.add(topic);
362         return;
363     }
364
365     /**
366      * Periodのキャプション文字列を返す。
367      * 主な用途はタブ画面の耳のラベルなど。
368      * @return キャプション文字列
369      */
370     public String getCaption(){
371         String result;
372
373         switch(getType()){
374         case PROLOGUE:
375             result = "プロローグ";
376             break;
377         case PROGRESS:
378             result = getDay() + "日目";
379             break;
380         case EPILOGUE:
381             result = "エピローグ";
382             break;
383         default:
384             assert false;
385             result = null;
386             break;
387         }
388
389         return result;
390     }
391
392     /**
393      * このPeriodをダウンロードしたときのログイン名を返す。
394      * @return ログイン名。ログアウト中はnull。
395      */
396     public String getLoginName(){
397         return this.loginName;
398     }
399
400     /**
401      * 公開発言番号にマッチする発言を返す。
402      * @param talkNo 公開発言番号
403      * @return 発言。見つからなければnull
404      */
405     public Talk getNumberedTalk(int talkNo){
406         if(talkNo <= 0) throw new IllegalArgumentException();
407
408         for(Topic topic : this.topicList){
409             if( ! (topic instanceof Talk) ) continue;
410             Talk talk = (Talk) topic;
411             if(talkNo == talk.getTalkNo()) return talk;
412         }
413
414         return null;
415     }
416
417     /**
418      * このPeriodの内容にゲーム進行上隠された部分がある可能性を判定する。
419      * @return 隠れた要素がありうるならfalse
420      */
421     public boolean isFullOpen(){
422         return this.isFullOpen;
423     }
424
425     /**
426      * ロード済みか否かチェックする。
427      * @return ロード済みならtrue
428      */
429     public boolean hasLoaded(){
430         return getTopics() > 0;
431     }
432
433     /**
434      * 発言データをアンロードする。
435      */
436     public void unload(){
437         this.limitHour = 0;
438         this.limitMinute = 0;
439         this.loginName = null;
440         this.isFullOpen = false;
441
442         this.isHot = false;
443
444         this.topicList.clear();
445
446         return;
447     }
448
449     /**
450      * 襲撃メッセージの有無を判定する。
451      * 決着が付くまで非狼陣営には見えない。
452      * 偽装GJでは狼にも見えない。
453      * @return 襲撃メッセージがあればtrue
454      */
455     public boolean hasAssaultTried(){
456         for(Topic topic : this.topicList){
457             if(topic instanceof Talk){
458                 Talk talk = (Talk) topic;
459                 if(talk.getTalkCount() <= 0) return true;
460             }else if(topic instanceof SysEvent){
461                 SysEvent sysEvent = (SysEvent) topic;
462                 SysEventType type = sysEvent.getSysEventType();
463                 if(type == SysEventType.ASSAULT) return true;
464             }
465         }
466
467         return false;
468     }
469
470     /**
471      * 処刑されたAvatarを返す。
472      * @return 処刑されたAvatar。突然死などなんらかの理由でいない場合はnull
473      */
474     public Avatar getExecutedAvatar(){
475         Avatar result = null;
476
477         for(Topic topic : getTopicList()){
478             if( ! (topic instanceof SysEvent) ) continue;
479             SysEvent event = (SysEvent) topic;
480             result = event.getExecutedAvatar();
481             if(result != null) break;
482         }
483
484         return result;
485     }
486
487     /**
488      * 投票に参加したAvatarの集合を返す。
489      * @return 投票に参加したAvatarのSet
490      */
491     public Set<Avatar> getVoterSet(){
492         Set<Avatar> result = new HashSet<Avatar>();
493
494         for(Topic topic : getTopicList()){
495             if( ! (topic instanceof SysEvent) ) continue;
496             SysEvent event = (SysEvent) topic;
497             result = event.getVoterSet(result);
498         }
499
500         return result;
501     }
502
503     /**
504      * 任意のタイプのシステムイベントを返す。
505      * 複数存在する場合、返すのは最初の一つだけ。
506      * @param type イベントタイプ
507      * @return システムイベント
508      */
509     public SysEvent getTypedSysEvent(SysEventType type){
510         for(Topic topic : getTopicList()){
511             if( ! (topic instanceof SysEvent) ) continue;
512             SysEvent event = (SysEvent) topic;
513             if(event.getSysEventType() == type) return event;
514         }
515
516         return null;
517     }
518
519     /**
520      * Periodパース用ハンドラ。
521      */
522     private static class PeriodHandler extends HtmlAdapter{
523
524         private static final int TALKTYPE_NUM = TalkType.values().length;
525
526         private final EntityConverter converter =
527                 new EntityConverter();
528
529         private final Map<Avatar, int[]> countMap =
530                 new HashMap<Avatar, int[]>();
531
532         private Period period = null;
533
534         private TalkType talkType;
535         private Avatar avatar;
536         private int talkNo;
537         private String anchorId;
538         private int talkHour;
539         private int talkMinute;
540         private DecodedContent talkContent = null;
541
542         private EventFamily eventFamily;
543         private SysEventType sysEventType;
544         private DecodedContent eventContent = null;
545         private final List<Avatar> avatarList = new LinkedList<Avatar>();
546         private final List<GameRole> roleList = new LinkedList<GameRole>();
547         private final List<Integer> integerList = new LinkedList<Integer>();
548         private final List<CharSequence>  charseqList =
549             new LinkedList<CharSequence>();
550
551         /**
552          * コンストラクタ。
553          */
554         public PeriodHandler(){
555             super();
556             return;
557         }
558
559         /**
560          * パース結果を格納するPeriodを設定する。
561          * @param period Period
562          */
563         public void setPeriod(Period period){
564             this.period = period;
565             return;
566         }
567
568         /**
569          * 文字列断片からAvatarを得る。
570          * 村に未登録のAvatarであればついでに登録される。
571          * @param content 文字列
572          * @param range 文字列内のAvatarフルネームを示す領域
573          * @return Avatar
574          */
575         private Avatar toAvatar(DecodedContent content, SeqRange range){
576             Village village = this.period.getVillage();
577             String fullName = this.converter
578                                   .convert(content, range)
579                                   .toString();
580             Avatar result = village.getAvatar(fullName);
581             if(result == null){
582                 result = new Avatar(fullName);
583                 village.addAvatar(result);
584             }
585
586             return result;
587         }
588
589         /**
590          * Avatar別、会話種ごとに発言回数をカウントする。
591          * 1から始まる。
592          * @param targetAvatar 対象Avatar
593          * @param targetType 対象会話種
594          * @return カウント数
595          */
596         private int countUp(Avatar targetAvatar, TalkType targetType){
597             int[] countArray = this.countMap.get(targetAvatar);
598             if(countArray == null){
599                 countArray = new int[TALKTYPE_NUM];
600                 this.countMap.put(targetAvatar, countArray);
601             }
602             int count = ++countArray[targetType.ordinal()];
603             return count;
604         }
605
606         /**
607          * {@inheritDoc}
608          * @param content {@inheritDoc}
609          * @throws HtmlParseException {@inheritDoc}
610          */
611         @Override
612         public void startParse(DecodedContent content)
613                 throws HtmlParseException{
614             this.period.loginName = null;
615             this.period.topicList.clear();
616             this.countMap.clear();
617             return;
618         }
619
620         /**
621          * {@inheritDoc}
622          * @param content {@inheritDoc}
623          * @param loginRange {@inheritDoc}
624          * @throws HtmlParseException {@inheritDoc}
625          */
626         @Override
627         public void loginName(DecodedContent content, SeqRange loginRange)
628                 throws HtmlParseException{
629             DecodedContent loginName =
630                     this.converter.convert(content, loginRange);
631
632             this.period.loginName = loginName.toString();
633
634             return;
635         }
636
637         /**
638          * {@inheritDoc}
639          * @param type {@inheritDoc}
640          * @throws HtmlParseException {@inheritDoc}
641          */
642         @Override
643         public void pageType(PageType type) throws HtmlParseException{
644             if(type != PageType.PERIOD_PAGE){
645                 throw new HtmlParseException(
646                         "意図しないページを読み込もうとしました。");
647             }
648             return;
649         }
650
651         /**
652          * {@inheritDoc}
653          * @param month {@inheritDoc}
654          * @param day {@inheritDoc}
655          * @param hour {@inheritDoc}
656          * @param minute {@inheritDoc}
657          * @throws HtmlParseException {@inheritDoc}
658          */
659         @Override
660         public void commitTime(int month, int day, int hour, int minute)
661                 throws HtmlParseException{
662             this.period.limitHour   = hour;
663             this.period.limitMinute = minute;
664             return;
665         }
666
667         /**
668          * {@inheritDoc}
669          * 自分へのリンクが無いかチェックする。
670          * 自分へのリンクが見つかればこのPeriodを非Hotにする。
671          * 自分へのリンクがあるということは、
672          * 今読んでるHTMLは別のPeriodのために書かれたものということ。
673          * 考えられる原因は、HotだったPeriodがゲーム進行に従い
674          * Hotでなくなったこと。
675          * @param content {@inheritDoc}
676          * @param anchorRange {@inheritDoc}
677          * @param periodType {@inheritDoc}
678          * @param day {@inheritDoc}
679          * @throws HtmlParseException {@inheritDoc}
680          */
681         @Override
682         public void periodLink(DecodedContent content,
683                                 SeqRange anchorRange,
684                                 PeriodType periodType,
685                                 int day)
686                 throws HtmlParseException{
687
688             if(this.period.getType() != periodType) return;
689
690             if(   periodType == PeriodType.PROGRESS
691                && this.period.getDay() != day ){
692                 return;
693             }
694
695             if( ! anchorRange.isValid() ) return;
696
697             this.period.setHot(false);
698
699             return;
700         }
701
702         /**
703          * {@inheritDoc}
704          * @throws HtmlParseException {@inheritDoc}
705          */
706         @Override
707         public void startTalk() throws HtmlParseException{
708             this.talkType = null;
709             this.avatar = null;
710             this.talkNo = -1;
711             this.anchorId = null;
712             this.talkHour = -1;
713             this.talkMinute = -1;
714             this.talkContent = new DecodedContent(100 + 1);
715
716             return;
717         }
718
719         /**
720          * {@inheritDoc}
721          * @param type {@inheritDoc}
722          * @throws HtmlParseException {@inheritDoc}
723          */
724         @Override
725         public void talkType(TalkType type)
726                 throws HtmlParseException{
727             this.talkType = type;
728             return;
729         }
730
731         /**
732          * {@inheritDoc}
733          * @param content {@inheritDoc}
734          * @param avatarRange {@inheritDoc}
735          * @throws HtmlParseException {@inheritDoc}
736          */
737         @Override
738         public void talkAvatar(DecodedContent content, SeqRange avatarRange)
739                 throws HtmlParseException{
740             this.avatar = toAvatar(content, avatarRange);
741             return;
742         }
743
744         /**
745          * {@inheritDoc}
746          * @param hour {@inheritDoc}
747          * @param minute {@inheritDoc}
748          * @throws HtmlParseException {@inheritDoc}
749          */
750         @Override
751         public void talkTime(int hour, int minute)
752                 throws HtmlParseException{
753             this.talkHour = hour;
754             this.talkMinute = minute;
755             return;
756         }
757
758         /**
759          * {@inheritDoc}
760          * @param tno {@inheritDoc}
761          * @throws HtmlParseException {@inheritDoc}
762          */
763         @Override
764         public void talkNo(int tno) throws HtmlParseException{
765             this.talkNo = tno;
766             return;
767         }
768
769         /**
770          * {@inheritDoc}
771          * @param content {@inheritDoc}
772          * @param idRange {@inheritDoc}
773          * @throws HtmlParseException {@inheritDoc}
774          */
775         @Override
776         public void talkId(DecodedContent content, SeqRange idRange)
777                 throws HtmlParseException{
778             this.anchorId = content.subSequence(idRange.getStartPos(),
779                                                 idRange.getEndPos()   )
780                                    .toString();
781             return;
782         }
783
784         /**
785          * {@inheritDoc}
786          * @param content {@inheritDoc}
787          * @param textRange {@inheritDoc}
788          * @throws HtmlParseException {@inheritDoc}
789          */
790         @Override
791         public void talkText(DecodedContent content, SeqRange textRange)
792                 throws HtmlParseException{
793             this.converter.append(this.talkContent, content, textRange);
794             return;
795         }
796
797         /**
798          * {@inheritDoc}
799          * @throws HtmlParseException {@inheritDoc}
800          */
801         @Override
802         public void talkBreak()
803                 throws HtmlParseException{
804             this.talkContent.append('\n');
805             return;
806         }
807
808         /**
809          * {@inheritDoc}
810          * @throws HtmlParseException {@inheritDoc}
811          */
812         @Override
813         public void endTalk() throws HtmlParseException{
814             Talk talk = new Talk(this.period,
815                                  this.talkType,
816                                  this.avatar,
817                                  this.talkNo,
818                                  this.anchorId,
819                                  this.talkHour, this.talkMinute,
820                                  this.talkContent );
821
822             int count = countUp(this.avatar, this.talkType);
823             talk.setCount(count);
824
825             this.period.addTopic(talk);
826
827             this.talkType = null;
828             this.avatar = null;
829             this.talkNo = -1;
830             this.anchorId = null;
831             this.talkHour = -1;
832             this.talkMinute = -1;
833             this.talkContent = null;
834
835             return;
836         }
837
838         /**
839          * {@inheritDoc}
840          * @param family {@inheritDoc}
841          * @throws HtmlParseException {@inheritDoc}
842          */
843         @Override
844         public void startSysEvent(EventFamily family)
845                 throws HtmlParseException{
846             this.eventFamily = family;
847             this.sysEventType = null;
848             this.eventContent = new DecodedContent();
849             this.avatarList.clear();
850             this.roleList.clear();
851             this.integerList.clear();
852             this.charseqList.clear();
853             return;
854         }
855
856         /**
857          * {@inheritDoc}
858          * @param type {@inheritDoc}
859          * @throws HtmlParseException {@inheritDoc}
860          */
861         @Override
862         public void sysEventType(SysEventType type)
863                 throws HtmlParseException{
864             this.sysEventType = type;
865             return;
866         }
867
868         /**
869          * {@inheritDoc}
870          * @param content {@inheritDoc}
871          * @param contentRange {@inheritDoc}
872          * @throws HtmlParseException {@inheritDoc}
873          */
874         @Override
875         public void sysEventContent(DecodedContent content,
876                                       SeqRange contentRange)
877                 throws HtmlParseException{
878             this.converter.append(this.eventContent, content, contentRange);
879             return;
880         }
881
882         /**
883          * {@inheritDoc}
884          * @param content {@inheritDoc}
885          * @param anchorRange {@inheritDoc}
886          * @param contentRange {@inheritDoc}
887          * @throws HtmlParseException {@inheritDoc}
888          */
889         @Override
890         public void sysEventContentAnchor(DecodedContent content,
891                                              SeqRange anchorRange,
892                                              SeqRange contentRange)
893                 throws HtmlParseException{
894             this.converter.append(this.eventContent, content, contentRange);
895             return;
896         }
897
898         /**
899          * {@inheritDoc}
900          * @throws HtmlParseException {@inheritDoc}
901          */
902         @Override
903         public void sysEventContentBreak() throws HtmlParseException{
904             this.eventContent.append('\n');
905             return;
906         }
907
908         /**
909          * {@inheritDoc}
910          * @param content {@inheritDoc}
911          * @param entryNo {@inheritDoc}
912          * @param avatarRange {@inheritDoc}
913          * @throws HtmlParseException {@inheritDoc}
914          */
915         @Override
916         public void sysEventOnStage(DecodedContent content,
917                                       int entryNo,
918                                       SeqRange avatarRange)
919                 throws HtmlParseException{
920             Avatar newAvatar = toAvatar(content, avatarRange);
921             this.integerList.add(entryNo);
922             this.avatarList.add(newAvatar);
923             return;
924         }
925
926         /**
927          * {@inheritDoc}
928          * @param role {@inheritDoc}
929          * @param num {@inheritDoc}
930          * @throws HtmlParseException {@inheritDoc}
931          */
932         @Override
933         public void sysEventOpenRole(GameRole role, int num)
934                 throws HtmlParseException{
935             this.roleList.add(role);
936             this.integerList.add(num);
937             return;
938         }
939
940         /**
941          * {@inheritDoc}
942          * @param content {@inheritDoc}
943          * @param avatarRange {@inheritDoc}
944          * @throws HtmlParseException {@inheritDoc}
945          */
946         @Override
947         public void sysEventMurdered(DecodedContent content,
948                                        SeqRange avatarRange)
949                 throws HtmlParseException{
950             Avatar murdered = toAvatar(content, avatarRange);
951             this.avatarList.add(murdered);
952             return;
953         }
954
955         /**
956          * {@inheritDoc}
957          * @param content {@inheritDoc}
958          * @param avatarRange {@inheritDoc}
959          * @throws HtmlParseException {@inheritDoc}
960          */
961         @Override
962         public void sysEventSurvivor(DecodedContent content,
963                                        SeqRange avatarRange)
964                 throws HtmlParseException{
965             Avatar survivor = toAvatar(content, avatarRange);
966             this.avatarList.add(survivor);
967             return;
968         }
969
970         /**
971          * {@inheritDoc}
972          * @param content {@inheritDoc}
973          * @param voteByRange {@inheritDoc}
974          * @param voteToRange {@inheritDoc}
975          * @throws HtmlParseException {@inheritDoc}
976          */
977         @Override
978         public void sysEventCounting(DecodedContent content,
979                                        SeqRange voteByRange,
980                                        SeqRange voteToRange)
981                 throws HtmlParseException{
982             if(voteByRange.isValid()){
983                 Avatar voteBy = toAvatar(content, voteByRange);
984                 this.avatarList.add(voteBy);
985             }
986             Avatar voteTo = toAvatar(content, voteToRange);
987             this.avatarList.add(voteTo);
988             return;
989         }
990
991         /**
992          * {@inheritDoc}
993          * @param content {@inheritDoc}
994          * @param voteByRange {@inheritDoc}
995          * @param voteToRange {@inheritDoc}
996          * @throws HtmlParseException {@inheritDoc}
997          */
998         @Override
999         public void sysEventCounting2(DecodedContent content,
1000                                         SeqRange voteByRange,
1001                                         SeqRange voteToRange)
1002                 throws HtmlParseException{
1003             sysEventCounting(content, voteByRange, voteToRange);
1004             return;
1005         }
1006
1007         /**
1008          * {@inheritDoc}
1009          * @param content {@inheritDoc}
1010          * @param avatarRange {@inheritDoc}
1011          * @throws HtmlParseException {@inheritDoc}
1012          */
1013         @Override
1014         public void sysEventSuddenDeath(DecodedContent content,
1015                                            SeqRange avatarRange)
1016                 throws HtmlParseException{
1017             Avatar suddenDeath = toAvatar(content, avatarRange);
1018             this.avatarList.add(suddenDeath);
1019             return;
1020         }
1021
1022         /**
1023          * {@inheritDoc}
1024          * @param content {@inheritDoc}
1025          * @param avatarRange {@inheritDoc}
1026          * @param anchorRange {@inheritDoc}
1027          * @param loginRange {@inheritDoc}
1028          * @param isLiving {@inheritDoc}
1029          * @param role {@inheritDoc}
1030          * @throws HtmlParseException {@inheritDoc}
1031          */
1032         @Override
1033         public void sysEventPlayerList(DecodedContent content,
1034                                           SeqRange avatarRange,
1035                                           SeqRange anchorRange,
1036                                           SeqRange loginRange,
1037                                           boolean isLiving,
1038                                           GameRole role )
1039                 throws HtmlParseException{
1040             Avatar who = toAvatar(content, avatarRange);
1041
1042             CharSequence anchor;
1043             if(anchorRange.isValid()){
1044                 anchor = this.converter.convert(content, anchorRange);
1045             }else{
1046                 anchor = "";
1047             }
1048             CharSequence account = this.converter
1049                                        .convert(content, loginRange);
1050
1051             Integer liveOrDead;
1052             if(isLiving) liveOrDead = Integer.valueOf(1);
1053             else         liveOrDead = Integer.valueOf(0);
1054
1055             this.avatarList.add(who);
1056             this.charseqList.add(anchor);
1057             this.charseqList.add(account);
1058             this.integerList.add(liveOrDead);
1059             this.roleList.add(role);
1060
1061             return;
1062         }
1063
1064         /**
1065          * {@inheritDoc}
1066          * @param content {@inheritDoc}
1067          * @param avatarRange {@inheritDoc}
1068          * @param votes {@inheritDoc}
1069          * @throws HtmlParseException {@inheritDoc}
1070          */
1071         @Override
1072         public void sysEventExecution(DecodedContent content,
1073                                         SeqRange avatarRange,
1074                                         int votes )
1075                 throws HtmlParseException{
1076             Avatar who = toAvatar(content, avatarRange);
1077
1078             this.avatarList.add(who);
1079             this.integerList.add(votes);
1080
1081             return;
1082         }
1083
1084         /**
1085          * {@inheritDoc}
1086          * @param hour {@inheritDoc}
1087          * @param minute {@inheritDoc}
1088          * @param minLimit {@inheritDoc}
1089          * @param maxLimit {@inheritDoc}
1090          * @throws HtmlParseException {@inheritDoc}
1091          */
1092         @Override
1093         public void sysEventAskEntry(int hour, int minute,
1094                                        int minLimit, int maxLimit)
1095                 throws HtmlParseException{
1096             this.integerList.add(hour * 60 + minute);
1097             this.integerList.add(minLimit);
1098             this.integerList.add(maxLimit);
1099             return;
1100         }
1101
1102         /**
1103          * {@inheritDoc}
1104          * @param hour {@inheritDoc}
1105          * @param minute {@inheritDoc}
1106          * @throws HtmlParseException {@inheritDoc}
1107          */
1108         @Override
1109         public void sysEventAskCommit(int hour, int minute)
1110                 throws HtmlParseException{
1111             this.integerList.add(hour * 60 + minute);
1112             return;
1113         }
1114
1115         /**
1116          * {@inheritDoc}
1117          * @param content {@inheritDoc}
1118          * @param avatarRange {@inheritDoc}
1119          * @throws HtmlParseException {@inheritDoc}
1120          */
1121         @Override
1122         public void sysEventNoComment(DecodedContent content,
1123                                         SeqRange avatarRange)
1124                 throws HtmlParseException{
1125             Avatar noComAvatar = toAvatar(content, avatarRange);
1126             this.avatarList.add(noComAvatar);
1127             return;
1128         }
1129
1130         /**
1131          * {@inheritDoc}
1132          * @param winner {@inheritDoc}
1133          * @param hour {@inheritDoc}
1134          * @param minute {@inheritDoc}
1135          * @throws HtmlParseException {@inheritDoc}
1136          */
1137         @Override
1138         public void sysEventStayEpilogue(Team winner, int hour, int minute)
1139                 throws HtmlParseException{
1140             GameRole role = null;
1141
1142             switch(winner){
1143             case VILLAGE: role = GameRole.INNOCENT; break;
1144             case WOLF:    role = GameRole.WOLF;     break;
1145             case HAMSTER: role = GameRole.HAMSTER;  break;
1146             default: assert false; break;
1147             }
1148
1149             this.roleList.add(role);
1150             this.integerList.add(hour * 60 + minute);
1151
1152             return;
1153         }
1154
1155         /**
1156          * {@inheritDoc}
1157          * @param content {@inheritDoc}
1158          * @param guardByRange {@inheritDoc}
1159          * @param guardToRange {@inheritDoc}
1160          * @throws HtmlParseException {@inheritDoc}
1161          */
1162         @Override
1163         public void sysEventGuard(DecodedContent content,
1164                                     SeqRange guardByRange,
1165                                     SeqRange guardToRange)
1166                 throws HtmlParseException{
1167             Avatar guardBy = toAvatar(content, guardByRange);
1168             Avatar guardTo = toAvatar(content, guardToRange);
1169             this.avatarList.add(guardBy);
1170             this.avatarList.add(guardTo);
1171             return;
1172         }
1173
1174         /**
1175          * {@inheritDoc}
1176          * @param content {@inheritDoc}
1177          * @param judgeByRange {@inheritDoc}
1178          * @param judgeToRange {@inheritDoc}
1179          * @throws HtmlParseException {@inheritDoc}
1180          */
1181         @Override
1182         public void sysEventJudge(DecodedContent content,
1183                                     SeqRange judgeByRange,
1184                                     SeqRange judgeToRange)
1185                 throws HtmlParseException{
1186             Avatar judgeBy = toAvatar(content, judgeByRange);
1187             Avatar judgeTo = toAvatar(content, judgeToRange);
1188             this.avatarList.add(judgeBy);
1189             this.avatarList.add(judgeTo);
1190             return;
1191         }
1192
1193         /**
1194          * {@inheritDoc}
1195          * @throws HtmlParseException {@inheritDoc}
1196          */
1197         @Override
1198         public void endSysEvent() throws HtmlParseException{
1199             SysEvent event = new SysEvent();
1200             event.setEventFamily(this.eventFamily);
1201             event.setSysEventType(this.sysEventType);
1202             event.setContent(this.eventContent);
1203             event.addAvatarList(this.avatarList);
1204             event.addRoleList(this.roleList);
1205             event.addIntegerList(this.integerList);
1206             event.addCharSequenceList(this.charseqList);
1207
1208             this.period.addTopic(event);
1209
1210             if(   this.sysEventType == SysEventType.MURDERED
1211                || this.sysEventType == SysEventType.NOMURDER ){
1212                 for(Topic topic : this.period.topicList){
1213                     if( ! (topic instanceof Talk) ) continue;
1214                     Talk talk = (Talk) topic;
1215                     if(talk.getTalkType() != TalkType.WOLFONLY) continue;
1216                     if( ! StringUtils
1217                          .isTerminated(talk.getDialog(),
1218                                        "!\u0020今日がお前の命日だ!") ){
1219                         continue;
1220                     }
1221                     talk.setCount(-1);
1222                     this.countMap.clear();
1223                 }
1224             }
1225
1226             this.eventFamily = null;
1227             this.sysEventType = null;
1228             this.eventContent = null;
1229             this.avatarList.clear();
1230             this.roleList.clear();
1231             this.integerList.clear();
1232             this.charseqList.clear();
1233
1234             return;
1235         }
1236
1237         /**
1238          * {@inheritDoc}
1239          * @throws HtmlParseException {@inheritDoc}
1240          */
1241         @Override
1242         public void endParse() throws HtmlParseException{
1243             return;
1244         }
1245
1246         // TODO 村名のチェックは不要か?
1247     }
1248
1249 }