OSDN Git Service

Merge branch 'Branch_release-'
[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.osdn.jindolf.parser.EntityConverter;
21 import jp.osdn.jindolf.parser.HtmlAdapter;
22 import jp.osdn.jindolf.parser.HtmlParseException;
23 import jp.osdn.jindolf.parser.HtmlParser;
24 import jp.osdn.jindolf.parser.PageType;
25 import jp.osdn.jindolf.parser.SeqRange;
26 import jp.osdn.jindolf.parser.content.DecodedContent;
27 import jp.sfjp.jindolf.net.HtmlSequence;
28 import jp.sfjp.jindolf.net.ServerAccess;
29 import jp.sfjp.jindolf.util.StringUtils;
30 import jp.sourceforge.jindolf.corelib.EventFamily;
31 import jp.sourceforge.jindolf.corelib.GameRole;
32 import jp.sourceforge.jindolf.corelib.LandDef;
33 import jp.sourceforge.jindolf.corelib.PeriodType;
34 import jp.sourceforge.jindolf.corelib.SysEventType;
35 import jp.sourceforge.jindolf.corelib.TalkType;
36 import jp.sourceforge.jindolf.corelib.Team;
37 import jp.sourceforge.jindolf.corelib.VillageState;
38
39 /**
40  * いわゆる「日」。
41  * 村の進行の一区切り。プロローグやエピローグも含まれる。
42  *
43  * <p>将来、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<>();
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<>();
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(true);
528         // TODO: SMP面文字に彩色対応するまでの暫定措置
529
530         private final Map<Avatar, int[]> countMap =
531                 new HashMap<>();
532
533         private Period period = null;
534
535         private TalkType talkType;
536         private Avatar avatar;
537         private int talkNo;
538         private String anchorId;
539         private int talkHour;
540         private int talkMinute;
541         private DecodedContent talkContent = null;
542
543         private EventFamily eventFamily;
544         private SysEventType sysEventType;
545         private DecodedContent eventContent = null;
546         private final List<Avatar> avatarList = new LinkedList<>();
547         private final List<GameRole> roleList = new LinkedList<>();
548         private final List<Integer> integerList = new LinkedList<>();
549         private final List<CharSequence>  charseqList =
550             new LinkedList<>();
551
552         /**
553          * コンストラクタ。
554          */
555         public PeriodHandler(){
556             super();
557             return;
558         }
559
560         /**
561          * パース結果を格納するPeriodを設定する。
562          * @param period Period
563          */
564         public void setPeriod(Period period){
565             this.period = period;
566             return;
567         }
568
569         /**
570          * 文字列断片からAvatarを得る。
571          * 村に未登録のAvatarであればついでに登録される。
572          * @param content 文字列
573          * @param range 文字列内のAvatarフルネームを示す領域
574          * @return Avatar
575          */
576         private Avatar toAvatar(DecodedContent content, SeqRange range){
577             Village village = this.period.getVillage();
578             String fullName = this.converter
579                                   .convert(content, range)
580                                   .toString();
581             Avatar result = village.getAvatar(fullName);
582             if(result == null){
583                 result = new Avatar(fullName);
584                 village.addAvatar(result);
585             }
586
587             return result;
588         }
589
590         /**
591          * Avatar別、会話種ごとに発言回数をカウントする。
592          * 1から始まる。
593          * @param targetAvatar 対象Avatar
594          * @param targetType 対象会話種
595          * @return カウント数
596          */
597         private int countUp(Avatar targetAvatar, TalkType targetType){
598             int[] countArray = this.countMap.get(targetAvatar);
599             if(countArray == null){
600                 countArray = new int[TALKTYPE_NUM];
601                 this.countMap.put(targetAvatar, countArray);
602             }
603             int count = ++countArray[targetType.ordinal()];
604             return count;
605         }
606
607         /**
608          * {@inheritDoc}
609          * @param content {@inheritDoc}
610          * @throws HtmlParseException {@inheritDoc}
611          */
612         @Override
613         public void startParse(DecodedContent content)
614                 throws HtmlParseException{
615             this.period.loginName = null;
616             this.period.topicList.clear();
617             this.countMap.clear();
618             return;
619         }
620
621         /**
622          * {@inheritDoc}
623          * @param content {@inheritDoc}
624          * @param loginRange {@inheritDoc}
625          * @throws HtmlParseException {@inheritDoc}
626          */
627         @Override
628         public void loginName(DecodedContent content, SeqRange loginRange)
629                 throws HtmlParseException{
630             DecodedContent loginName =
631                     this.converter.convert(content, loginRange);
632
633             this.period.loginName = loginName.toString();
634
635             return;
636         }
637
638         /**
639          * {@inheritDoc}
640          * @param type {@inheritDoc}
641          * @throws HtmlParseException {@inheritDoc}
642          */
643         @Override
644         public void pageType(PageType type) throws HtmlParseException{
645             if(type != PageType.PERIOD_PAGE){
646                 throw new HtmlParseException(
647                         "意図しないページを読み込もうとしました。");
648             }
649             return;
650         }
651
652         /**
653          * {@inheritDoc}
654          * @param month {@inheritDoc}
655          * @param day {@inheritDoc}
656          * @param hour {@inheritDoc}
657          * @param minute {@inheritDoc}
658          * @throws HtmlParseException {@inheritDoc}
659          */
660         @Override
661         public void commitTime(int month, int day, int hour, int minute)
662                 throws HtmlParseException{
663             this.period.limitHour   = hour;
664             this.period.limitMinute = minute;
665             return;
666         }
667
668         /**
669          * {@inheritDoc}
670          * 自分へのリンクが無いかチェックする。
671          * 自分へのリンクが見つかればこのPeriodを非Hotにする。
672          * 自分へのリンクがあるということは、
673          * 今読んでるHTMLは別のPeriodのために書かれたものということ。
674          * 考えられる原因は、HotだったPeriodがゲーム進行に従い
675          * Hotでなくなったこと。
676          * @param content {@inheritDoc}
677          * @param anchorRange {@inheritDoc}
678          * @param periodType {@inheritDoc}
679          * @param day {@inheritDoc}
680          * @throws HtmlParseException {@inheritDoc}
681          */
682         @Override
683         public void periodLink(DecodedContent content,
684                                 SeqRange anchorRange,
685                                 PeriodType periodType,
686                                 int day)
687                 throws HtmlParseException{
688
689             if(this.period.getType() != periodType) return;
690
691             if(    periodType == PeriodType.PROGRESS
692                 && this.period.getDay() != day ){
693                 return;
694             }
695
696             if( ! anchorRange.isValid() ) return;
697
698             this.period.setHot(false);
699
700             return;
701         }
702
703         /**
704          * {@inheritDoc}
705          * @throws HtmlParseException {@inheritDoc}
706          */
707         @Override
708         public void startTalk() throws HtmlParseException{
709             this.talkType = null;
710             this.avatar = null;
711             this.talkNo = -1;
712             this.anchorId = null;
713             this.talkHour = -1;
714             this.talkMinute = -1;
715             this.talkContent = new DecodedContent(100 + 1);
716
717             return;
718         }
719
720         /**
721          * {@inheritDoc}
722          * @param type {@inheritDoc}
723          * @throws HtmlParseException {@inheritDoc}
724          */
725         @Override
726         public void talkType(TalkType type)
727                 throws HtmlParseException{
728             this.talkType = type;
729             return;
730         }
731
732         /**
733          * {@inheritDoc}
734          * @param content {@inheritDoc}
735          * @param avatarRange {@inheritDoc}
736          * @throws HtmlParseException {@inheritDoc}
737          */
738         @Override
739         public void talkAvatar(DecodedContent content, SeqRange avatarRange)
740                 throws HtmlParseException{
741             this.avatar = toAvatar(content, avatarRange);
742             return;
743         }
744
745         /**
746          * {@inheritDoc}
747          * @param hour {@inheritDoc}
748          * @param minute {@inheritDoc}
749          * @throws HtmlParseException {@inheritDoc}
750          */
751         @Override
752         public void talkTime(int hour, int minute)
753                 throws HtmlParseException{
754             this.talkHour = hour;
755             this.talkMinute = minute;
756             return;
757         }
758
759         /**
760          * {@inheritDoc}
761          * @param tno {@inheritDoc}
762          * @throws HtmlParseException {@inheritDoc}
763          */
764         @Override
765         public void talkNo(int tno) throws HtmlParseException{
766             this.talkNo = tno;
767             return;
768         }
769
770         /**
771          * {@inheritDoc}
772          * @param content {@inheritDoc}
773          * @param idRange {@inheritDoc}
774          * @throws HtmlParseException {@inheritDoc}
775          */
776         @Override
777         public void talkId(DecodedContent content, SeqRange idRange)
778                 throws HtmlParseException{
779             this.anchorId = content.subSequence(idRange.getStartPos(),
780                                                 idRange.getEndPos()   )
781                                    .toString();
782             return;
783         }
784
785         /**
786          * {@inheritDoc}
787          * @param content {@inheritDoc}
788          * @param textRange {@inheritDoc}
789          * @throws HtmlParseException {@inheritDoc}
790          */
791         @Override
792         public void talkText(DecodedContent content, SeqRange textRange)
793                 throws HtmlParseException{
794             this.converter.append(this.talkContent, content, textRange);
795             return;
796         }
797
798         /**
799          * {@inheritDoc}
800          * @throws HtmlParseException {@inheritDoc}
801          */
802         @Override
803         public void talkBreak()
804                 throws HtmlParseException{
805             this.talkContent.append('\n');
806             return;
807         }
808
809         /**
810          * {@inheritDoc}
811          * @throws HtmlParseException {@inheritDoc}
812          */
813         @Override
814         public void endTalk() throws HtmlParseException{
815             Talk talk = new Talk(this.period,
816                                  this.talkType,
817                                  this.avatar,
818                                  this.talkNo,
819                                  this.anchorId,
820                                  this.talkHour, this.talkMinute,
821                                  this.talkContent );
822
823             int count = countUp(this.avatar, this.talkType);
824             talk.setCount(count);
825
826             this.period.addTopic(talk);
827
828             this.talkType = null;
829             this.avatar = null;
830             this.talkNo = -1;
831             this.anchorId = null;
832             this.talkHour = -1;
833             this.talkMinute = -1;
834             this.talkContent = null;
835
836             return;
837         }
838
839         /**
840          * {@inheritDoc}
841          * @param family {@inheritDoc}
842          * @throws HtmlParseException {@inheritDoc}
843          */
844         @Override
845         public void startSysEvent(EventFamily family)
846                 throws HtmlParseException{
847             this.eventFamily = family;
848             this.sysEventType = null;
849             this.eventContent = new DecodedContent();
850             this.avatarList.clear();
851             this.roleList.clear();
852             this.integerList.clear();
853             this.charseqList.clear();
854             return;
855         }
856
857         /**
858          * {@inheritDoc}
859          * @param type {@inheritDoc}
860          * @throws HtmlParseException {@inheritDoc}
861          */
862         @Override
863         public void sysEventType(SysEventType type)
864                 throws HtmlParseException{
865             this.sysEventType = type;
866             return;
867         }
868
869         /**
870          * {@inheritDoc}
871          * @param content {@inheritDoc}
872          * @param contentRange {@inheritDoc}
873          * @throws HtmlParseException {@inheritDoc}
874          */
875         @Override
876         public void sysEventContent(DecodedContent content,
877                                       SeqRange contentRange)
878                 throws HtmlParseException{
879             this.converter.append(this.eventContent, content, contentRange);
880             return;
881         }
882
883         /**
884          * {@inheritDoc}
885          * @param content {@inheritDoc}
886          * @param anchorRange {@inheritDoc}
887          * @param contentRange {@inheritDoc}
888          * @throws HtmlParseException {@inheritDoc}
889          */
890         @Override
891         public void sysEventContentAnchor(DecodedContent content,
892                                              SeqRange anchorRange,
893                                              SeqRange contentRange)
894                 throws HtmlParseException{
895             this.converter.append(this.eventContent, content, contentRange);
896             return;
897         }
898
899         /**
900          * {@inheritDoc}
901          * @throws HtmlParseException {@inheritDoc}
902          */
903         @Override
904         public void sysEventContentBreak() throws HtmlParseException{
905             this.eventContent.append('\n');
906             return;
907         }
908
909         /**
910          * {@inheritDoc}
911          * @param content {@inheritDoc}
912          * @param entryNo {@inheritDoc}
913          * @param avatarRange {@inheritDoc}
914          * @throws HtmlParseException {@inheritDoc}
915          */
916         @Override
917         public void sysEventOnStage(DecodedContent content,
918                                       int entryNo,
919                                       SeqRange avatarRange)
920                 throws HtmlParseException{
921             Avatar newAvatar = toAvatar(content, avatarRange);
922             this.integerList.add(entryNo);
923             this.avatarList.add(newAvatar);
924             return;
925         }
926
927         /**
928          * {@inheritDoc}
929          * @param role {@inheritDoc}
930          * @param num {@inheritDoc}
931          * @throws HtmlParseException {@inheritDoc}
932          */
933         @Override
934         public void sysEventOpenRole(GameRole role, int num)
935                 throws HtmlParseException{
936             this.roleList.add(role);
937             this.integerList.add(num);
938             return;
939         }
940
941         /**
942          * {@inheritDoc}
943          * @param content {@inheritDoc}
944          * @param avatarRange {@inheritDoc}
945          * @throws HtmlParseException {@inheritDoc}
946          */
947         @Override
948         public void sysEventMurdered(DecodedContent content,
949                                        SeqRange avatarRange)
950                 throws HtmlParseException{
951             Avatar murdered = toAvatar(content, avatarRange);
952             this.avatarList.add(murdered);
953             return;
954         }
955
956         /**
957          * {@inheritDoc}
958          * @param content {@inheritDoc}
959          * @param avatarRange {@inheritDoc}
960          * @throws HtmlParseException {@inheritDoc}
961          */
962         @Override
963         public void sysEventSurvivor(DecodedContent content,
964                                        SeqRange avatarRange)
965                 throws HtmlParseException{
966             Avatar survivor = toAvatar(content, avatarRange);
967             this.avatarList.add(survivor);
968             return;
969         }
970
971         /**
972          * {@inheritDoc}
973          * @param content {@inheritDoc}
974          * @param voteByRange {@inheritDoc}
975          * @param voteToRange {@inheritDoc}
976          * @throws HtmlParseException {@inheritDoc}
977          */
978         @Override
979         public void sysEventCounting(DecodedContent content,
980                                        SeqRange voteByRange,
981                                        SeqRange voteToRange)
982                 throws HtmlParseException{
983             if(voteByRange.isValid()){
984                 Avatar voteBy = toAvatar(content, voteByRange);
985                 this.avatarList.add(voteBy);
986             }
987             Avatar voteTo = toAvatar(content, voteToRange);
988             this.avatarList.add(voteTo);
989             return;
990         }
991
992         /**
993          * {@inheritDoc}
994          * @param content {@inheritDoc}
995          * @param voteByRange {@inheritDoc}
996          * @param voteToRange {@inheritDoc}
997          * @throws HtmlParseException {@inheritDoc}
998          */
999         @Override
1000         public void sysEventCounting2(DecodedContent content,
1001                                         SeqRange voteByRange,
1002                                         SeqRange voteToRange)
1003                 throws HtmlParseException{
1004             sysEventCounting(content, voteByRange, voteToRange);
1005             return;
1006         }
1007
1008         /**
1009          * {@inheritDoc}
1010          * @param content {@inheritDoc}
1011          * @param avatarRange {@inheritDoc}
1012          * @throws HtmlParseException {@inheritDoc}
1013          */
1014         @Override
1015         public void sysEventSuddenDeath(DecodedContent content,
1016                                            SeqRange avatarRange)
1017                 throws HtmlParseException{
1018             Avatar suddenDeath = toAvatar(content, avatarRange);
1019             this.avatarList.add(suddenDeath);
1020             return;
1021         }
1022
1023         /**
1024          * {@inheritDoc}
1025          * @param content {@inheritDoc}
1026          * @param avatarRange {@inheritDoc}
1027          * @param anchorRange {@inheritDoc}
1028          * @param loginRange {@inheritDoc}
1029          * @param isLiving {@inheritDoc}
1030          * @param role {@inheritDoc}
1031          * @throws HtmlParseException {@inheritDoc}
1032          */
1033         @Override
1034         public void sysEventPlayerList(DecodedContent content,
1035                                           SeqRange avatarRange,
1036                                           SeqRange anchorRange,
1037                                           SeqRange loginRange,
1038                                           boolean isLiving,
1039                                           GameRole role )
1040                 throws HtmlParseException{
1041             Avatar who = toAvatar(content, avatarRange);
1042
1043             CharSequence anchor;
1044             if(anchorRange.isValid()){
1045                 anchor = this.converter.convert(content, anchorRange);
1046             }else{
1047                 anchor = "";
1048             }
1049             CharSequence account = this.converter
1050                                        .convert(content, loginRange);
1051
1052             Integer liveOrDead;
1053             if(isLiving) liveOrDead = 1;
1054             else         liveOrDead = 0;
1055
1056             this.avatarList.add(who);
1057             this.charseqList.add(anchor);
1058             this.charseqList.add(account);
1059             this.integerList.add(liveOrDead);
1060             this.roleList.add(role);
1061
1062             return;
1063         }
1064
1065         /**
1066          * {@inheritDoc}
1067          * @param content {@inheritDoc}
1068          * @param avatarRange {@inheritDoc}
1069          * @param votes {@inheritDoc}
1070          * @throws HtmlParseException {@inheritDoc}
1071          */
1072         @Override
1073         public void sysEventExecution(DecodedContent content,
1074                                         SeqRange avatarRange,
1075                                         int votes )
1076                 throws HtmlParseException{
1077             Avatar who = toAvatar(content, avatarRange);
1078
1079             this.avatarList.add(who);
1080             this.integerList.add(votes);
1081
1082             return;
1083         }
1084
1085         /**
1086          * {@inheritDoc}
1087          * @param hour {@inheritDoc}
1088          * @param minute {@inheritDoc}
1089          * @param minLimit {@inheritDoc}
1090          * @param maxLimit {@inheritDoc}
1091          * @throws HtmlParseException {@inheritDoc}
1092          */
1093         @Override
1094         public void sysEventAskEntry(int hour, int minute,
1095                                        int minLimit, int maxLimit)
1096                 throws HtmlParseException{
1097             this.integerList.add(hour * 60 + minute);
1098             this.integerList.add(minLimit);
1099             this.integerList.add(maxLimit);
1100             return;
1101         }
1102
1103         /**
1104          * {@inheritDoc}
1105          * @param hour {@inheritDoc}
1106          * @param minute {@inheritDoc}
1107          * @throws HtmlParseException {@inheritDoc}
1108          */
1109         @Override
1110         public void sysEventAskCommit(int hour, int minute)
1111                 throws HtmlParseException{
1112             this.integerList.add(hour * 60 + minute);
1113             return;
1114         }
1115
1116         /**
1117          * {@inheritDoc}
1118          * @param content {@inheritDoc}
1119          * @param avatarRange {@inheritDoc}
1120          * @throws HtmlParseException {@inheritDoc}
1121          */
1122         @Override
1123         public void sysEventNoComment(DecodedContent content,
1124                                         SeqRange avatarRange)
1125                 throws HtmlParseException{
1126             Avatar noComAvatar = toAvatar(content, avatarRange);
1127             this.avatarList.add(noComAvatar);
1128             return;
1129         }
1130
1131         /**
1132          * {@inheritDoc}
1133          * @param winner {@inheritDoc}
1134          * @param hour {@inheritDoc}
1135          * @param minute {@inheritDoc}
1136          * @throws HtmlParseException {@inheritDoc}
1137          */
1138         @Override
1139         public void sysEventStayEpilogue(Team winner, int hour, int minute)
1140                 throws HtmlParseException{
1141             GameRole role = null;
1142
1143             switch(winner){
1144             case VILLAGE: role = GameRole.INNOCENT; break;
1145             case WOLF:    role = GameRole.WOLF;     break;
1146             case HAMSTER: role = GameRole.HAMSTER;  break;
1147             default: assert false; break;
1148             }
1149
1150             this.roleList.add(role);
1151             this.integerList.add(hour * 60 + minute);
1152
1153             return;
1154         }
1155
1156         /**
1157          * {@inheritDoc}
1158          * @param content {@inheritDoc}
1159          * @param guardByRange {@inheritDoc}
1160          * @param guardToRange {@inheritDoc}
1161          * @throws HtmlParseException {@inheritDoc}
1162          */
1163         @Override
1164         public void sysEventGuard(DecodedContent content,
1165                                     SeqRange guardByRange,
1166                                     SeqRange guardToRange)
1167                 throws HtmlParseException{
1168             Avatar guardBy = toAvatar(content, guardByRange);
1169             Avatar guardTo = toAvatar(content, guardToRange);
1170             this.avatarList.add(guardBy);
1171             this.avatarList.add(guardTo);
1172             return;
1173         }
1174
1175         /**
1176          * {@inheritDoc}
1177          * @param content {@inheritDoc}
1178          * @param judgeByRange {@inheritDoc}
1179          * @param judgeToRange {@inheritDoc}
1180          * @throws HtmlParseException {@inheritDoc}
1181          */
1182         @Override
1183         public void sysEventJudge(DecodedContent content,
1184                                     SeqRange judgeByRange,
1185                                     SeqRange judgeToRange)
1186                 throws HtmlParseException{
1187             Avatar judgeBy = toAvatar(content, judgeByRange);
1188             Avatar judgeTo = toAvatar(content, judgeToRange);
1189             this.avatarList.add(judgeBy);
1190             this.avatarList.add(judgeTo);
1191             return;
1192         }
1193
1194         /**
1195          * {@inheritDoc}
1196          * @throws HtmlParseException {@inheritDoc}
1197          */
1198         @Override
1199         public void endSysEvent() throws HtmlParseException{
1200             SysEvent event = new SysEvent();
1201             event.setEventFamily(this.eventFamily);
1202             event.setSysEventType(this.sysEventType);
1203             event.setContent(this.eventContent);
1204             event.addAvatarList(this.avatarList);
1205             event.addRoleList(this.roleList);
1206             event.addIntegerList(this.integerList);
1207             event.addCharSequenceList(this.charseqList);
1208
1209             this.period.addTopic(event);
1210
1211             if(    this.sysEventType == SysEventType.MURDERED
1212                 || this.sysEventType == SysEventType.NOMURDER ){
1213                 for(Topic topic : this.period.topicList){
1214                     if( ! (topic instanceof Talk) ) continue;
1215                     Talk talk = (Talk) topic;
1216                     if(talk.getTalkType() != TalkType.WOLFONLY) continue;
1217                     if( ! StringUtils
1218                          .isTerminated(talk.getDialog(),
1219                                        "!\u0020今日がお前の命日だ!") ){
1220                         continue;
1221                     }
1222                     talk.setCount(-1);
1223                     this.countMap.clear();
1224                 }
1225             }
1226
1227             this.eventFamily = null;
1228             this.sysEventType = null;
1229             this.eventContent = null;
1230             this.avatarList.clear();
1231             this.roleList.clear();
1232             this.integerList.clear();
1233             this.charseqList.clear();
1234
1235             return;
1236         }
1237
1238         /**
1239          * {@inheritDoc}
1240          * @throws HtmlParseException {@inheritDoc}
1241          */
1242         @Override
1243         public void endParse() throws HtmlParseException{
1244             return;
1245         }
1246
1247         // TODO 村名のチェックは不要か?
1248     }
1249
1250 }