OSDN Git Service

PeriodLoader is thread safe now.
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / data / html / PeriodHandler.java
1 /*
2  * period handler
3  *
4  * License : The MIT License
5  * Copyright(c) 2020 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.data.html;
9
10 import java.util.HashMap;
11 import java.util.LinkedList;
12 import java.util.List;
13 import java.util.Map;
14 import jp.osdn.jindolf.parser.EntityConverter;
15 import jp.osdn.jindolf.parser.HtmlAdapter;
16 import jp.osdn.jindolf.parser.HtmlParseException;
17 import jp.osdn.jindolf.parser.PageType;
18 import jp.osdn.jindolf.parser.SeqRange;
19 import jp.osdn.jindolf.parser.content.DecodedContent;
20 import jp.sfjp.jindolf.data.Avatar;
21 import jp.sfjp.jindolf.data.Period;
22 import jp.sfjp.jindolf.data.SysEvent;
23 import jp.sfjp.jindolf.data.Talk;
24 import jp.sfjp.jindolf.data.Topic;
25 import jp.sfjp.jindolf.data.Village;
26 import jp.sourceforge.jindolf.corelib.EventFamily;
27 import jp.sourceforge.jindolf.corelib.GameRole;
28 import jp.sourceforge.jindolf.corelib.PeriodType;
29 import jp.sourceforge.jindolf.corelib.SysEventType;
30 import jp.sourceforge.jindolf.corelib.TalkType;
31 import jp.sourceforge.jindolf.corelib.Team;
32
33
34 /**
35  * 各日(Period)のHTMLをパースし、
36  * 会話やイベントの通知を受け取るためのハンドラ。
37  *
38  * <p>パース終了時には、
39  * あらかじめ指定したPeriodインスタンスに
40  * 会話やイベントのリストが適切に更新される。
41  *
42  * <p>各種ビューが対応するまでの間、Unicodeの非BMP面文字には代替文字で対処。
43  *
44  * <p>※ 人狼BBS:G国におけるG2087村のエピローグが終了した段階で、
45  * 人狼BBSは過去ログの提供しか行っていない。
46  * だがこのクラスには進行中の村の各日をパースするための
47  * 冗長な処理が若干残っている。
48  */
49 class PeriodHandler extends HtmlAdapter {
50
51     private static final int TALKTYPE_NUM = TalkType.values().length;
52
53     private final EntityConverter converter =
54             new EntityConverter(true);
55     // TODO: 非BMP面文字に対応するまでの暫定措置
56
57     /** 非別、Avatar別、会話種別の会話通し番号。 */
58     private final Map<Avatar, int[]> countMap =
59             new HashMap<>();
60
61     private Period period = null;
62
63     private TalkType talkType;
64     private Avatar avatar;
65     private int talkNo;
66     private String anchorId;
67     private int talkHour;
68     private int talkMinute;
69     private DecodedContent talkContent = null;
70
71     private EventFamily eventFamily;
72     private SysEventType sysEventType;
73     private DecodedContent eventContent = null;
74     private final List<Avatar> avatarList = new LinkedList<>();
75     private final List<GameRole> roleList = new LinkedList<>();
76     private final List<Integer> integerList = new LinkedList<>();
77     private final List<CharSequence>  charseqList =
78         new LinkedList<>();
79
80
81     /**
82      * コンストラクタ。
83      */
84     PeriodHandler(){
85         super();
86         return;
87     }
88
89
90     /**
91      * 更新対象のPeriodを設定する。
92      *
93      * @param period Period
94      */
95     void setPeriod(Period period){
96         this.period = period;
97         reset();
98         return;
99     }
100
101     /**
102      * フルネーム文字列からAvatarインスタンスを得る。
103      *
104      * <p>村に未登録のAvatarであればついでに登録される。
105      *
106      * @param content 文字列
107      * @param range 文字列内のAvatarフルネームを示す領域
108      * @return Avatar
109      */
110     private Avatar toAvatar(DecodedContent content, SeqRange range){
111         Village village = this.period.getVillage();
112         String fullName = this.converter
113                               .convert(content, range)
114                               .toString();
115         Avatar result = village.getAvatar(fullName);
116         if(result == null){
117             result = new Avatar(fullName);
118             village.addAvatar(result);
119         }
120
121         return result;
122     }
123
124     /**
125      * パース中の各種コンテキストをリセットする。
126      */
127     void reset(){
128         this.countMap.clear();
129
130         resetTalkContext();
131         resetEventContext();
132
133         return;
134     }
135
136     /**
137      * パース中の会話コンテキストをリセットする。
138      */
139     private void resetTalkContext(){
140         this.talkType = null;
141         this.avatar = null;
142         this.talkNo = -1;
143         this.anchorId = null;
144         this.talkHour = -1;
145         this.talkMinute = -1;
146         this.talkContent = null;
147         return;
148     }
149
150     /**
151      * パース中のイベントコンテキストをリセットする。
152      */
153     private void resetEventContext(){
154         this.eventFamily = null;
155         this.sysEventType = null;
156         this.eventContent = null;
157         this.avatarList.clear();
158         this.roleList.clear();
159         this.integerList.clear();
160         this.charseqList.clear();
161         return;
162     }
163
164     /**
165      * {@inheritDoc}
166      *
167      * @param content {@inheritDoc}
168      * @throws HtmlParseException {@inheritDoc}
169      */
170     @Override
171     public void startParse(DecodedContent content)
172             throws HtmlParseException{
173         reset();
174
175         this.period.setLoginName(null);
176         this.period.clearTopicList();
177
178         return;
179     }
180
181     /**
182      * {@inheritDoc}
183      *
184      * <p>各PeriodのHTML上部にあるログイン名が通知されたのなら、
185      * それはPOSTやCookieを使ってのログインに成功したと言うこと。
186      *
187      * <p>ログイン名中の文字実体参照は展開される。
188      *
189      * <p>※ 2020-02現在、人狼BBS各国へのログインは無意味。
190      *
191      * @param content {@inheritDoc}
192      * @param loginRange {@inheritDoc}
193      * @throws HtmlParseException {@inheritDoc}
194      */
195     @Override
196     public void loginName(DecodedContent content, SeqRange loginRange)
197             throws HtmlParseException{
198         DecodedContent loginName =
199                 this.converter.convert(content, loginRange);
200
201         this.period.setLoginName(loginName.toString());
202
203         return;
204     }
205
206     /**
207      * {@inheritDoc}
208      *
209      * <p>受信したHTMLがPeriodページでないのならパースを中止する。
210      *
211      * @param type {@inheritDoc}
212      * @throws HtmlParseException {@inheritDoc}
213      */
214     @Override
215     public void pageType(PageType type) throws HtmlParseException{
216         if(type != PageType.PERIOD_PAGE){
217             throw new HtmlParseException(
218                     "意図しないページを読み込もうとしました。");
219         }
220         return;
221     }
222
223     /**
224      * {@inheritDoc}
225      *
226      * <p>月日の通知は無視される。
227      *
228      * @param month {@inheritDoc}
229      * @param day {@inheritDoc}
230      * @param hour {@inheritDoc}
231      * @param minute {@inheritDoc}
232      * @throws HtmlParseException {@inheritDoc}
233      */
234     @Override
235     public void commitTime(int month, int day, int hour, int minute)
236             throws HtmlParseException{
237         this.period.setLimit(hour, minute);
238         return;
239     }
240
241     /**
242      * {@inheritDoc}
243      *
244      * <p>このPeriodが進行中(Hot!)か否か判定する。
245      *
246      * <p>PeriodのHTML内に自分自身へのリンクが無いかチェックする。
247      * 自分へのリンクが見つかればこのPeriodを非Hotにする。
248      * 自分へのリンクがあるということは、
249      * 今受信しているHTMLは別のPeriodから辿るために書かれたものということ。
250      *
251      * <p>原因としては、HotだったPeriodがゲーム進行に従い
252      * Hotでなくなったことなどが考えられる。
253      *
254      * <p>各Periodの種別と日は、
255      * 村情報受信を通じて事前に設定されていなければならない。
256      *
257      * <p>※ 2020-02現在、HotなPeriodを受信する機会はないはず。
258      *
259      * @param content {@inheritDoc}
260      * @param anchorRange {@inheritDoc}
261      * @param periodType {@inheritDoc}
262      * @param day {@inheritDoc}
263      * @throws HtmlParseException {@inheritDoc}
264      */
265     @Override
266     public void periodLink(DecodedContent content,
267                            SeqRange anchorRange,
268                            PeriodType periodType,
269                            int day )
270             throws HtmlParseException{
271         if(this.period.getType() != periodType) return;
272
273         boolean isProgress = periodType == PeriodType.PROGRESS;
274         boolean dayMatch = this.period.getDay() == day;
275         if(isProgress && ! dayMatch){
276             return;
277         }
278
279         if( ! anchorRange.isValid() ) return;
280
281         this.period.setHot(false);
282
283         return;
284     }
285
286     /**
287      * {@inheritDoc}
288      *
289      * @throws HtmlParseException {@inheritDoc}
290      */
291     @Override
292     public void startTalk() throws HtmlParseException{
293         resetTalkContext();
294         this.talkContent = new DecodedContent(100 + 1);
295         return;
296     }
297
298     /**
299      * {@inheritDoc}
300      *
301      * @param type {@inheritDoc}
302      * @throws HtmlParseException {@inheritDoc}
303      */
304     @Override
305     public void talkType(TalkType type)
306             throws HtmlParseException{
307         this.talkType = type;
308         return;
309     }
310
311     /**
312      * {@inheritDoc}
313      *
314      * @param content {@inheritDoc}
315      * @param avatarRange {@inheritDoc}
316      * @throws HtmlParseException {@inheritDoc}
317      */
318     @Override
319     public void talkAvatar(DecodedContent content, SeqRange avatarRange)
320             throws HtmlParseException{
321         this.avatar = toAvatar(content, avatarRange);
322         return;
323     }
324
325     /**
326      * {@inheritDoc}
327      *
328      * @param hour {@inheritDoc}
329      * @param minute {@inheritDoc}
330      * @throws HtmlParseException {@inheritDoc}
331      */
332     @Override
333     public void talkTime(int hour, int minute)
334             throws HtmlParseException{
335         this.talkHour = hour;
336         this.talkMinute = minute;
337         return;
338     }
339
340     /**
341      * {@inheritDoc}
342      *
343      * @param tno {@inheritDoc}
344      * @throws HtmlParseException {@inheritDoc}
345      */
346     @Override
347     public void talkNo(int tno) throws HtmlParseException{
348         this.talkNo = tno;
349         return;
350     }
351
352     /**
353      * {@inheritDoc}
354      *
355      * @param content {@inheritDoc}
356      * @param idRange {@inheritDoc}
357      * @throws HtmlParseException {@inheritDoc}
358      */
359     @Override
360     public void talkId(DecodedContent content, SeqRange idRange)
361             throws HtmlParseException{
362         this.anchorId = content.subSequence(idRange.getStartPos(),
363                                             idRange.getEndPos()   )
364                                .toString();
365         return;
366     }
367
368     /**
369      * {@inheritDoc}
370      *
371      * <p>会話中の文字実体参照は展開される。
372      *
373      * @param content {@inheritDoc}
374      * @param textRange {@inheritDoc}
375      * @throws HtmlParseException {@inheritDoc}
376      */
377     @Override
378     public void talkText(DecodedContent content, SeqRange textRange)
379             throws HtmlParseException{
380         this.converter.append(this.talkContent, content, textRange);
381         return;
382     }
383
384     /**
385      * {@inheritDoc}
386      *
387      * @throws HtmlParseException {@inheritDoc}
388      */
389     @Override
390     public void talkBreak()
391             throws HtmlParseException{
392         this.talkContent.append('\n');
393         return;
394     }
395
396     /**
397      * 日別、Avatar別、会話種ごとに発言回数をインクリメントする。
398      *
399      * @param targetAvatar 対象Avatar
400      * @param targetType 対象会話種
401      * @return 現時点でのカウント数
402      */
403     private int countUp(Avatar targetAvatar, TalkType targetType){
404         int[] avatarCount = this.countMap.get(targetAvatar);
405         if(avatarCount == null){
406             avatarCount = new int[TALKTYPE_NUM];
407             this.countMap.put(targetAvatar, avatarCount);
408         }
409
410         int typeIdx = targetType.ordinal();
411         int count = ++avatarCount[typeIdx];
412
413         return count;
414     }
415
416     /**
417      * {@inheritDoc}
418      *
419      * <p>パース中の各種コンテキストから会話を組み立て、
420      * Periodに追加する。
421      *
422      * @throws HtmlParseException {@inheritDoc}
423      */
424     @Override
425     public void endTalk() throws HtmlParseException{
426         Talk talk = new Talk(this.period,
427                              this.talkType,
428                              this.avatar,
429                              this.talkNo,
430                              this.anchorId,
431                              this.talkHour, this.talkMinute,
432                              this.talkContent );
433
434         int count = countUp(this.avatar, this.talkType);
435         talk.setCount(count);
436
437         this.period.addTopic(talk);
438
439         resetTalkContext();
440
441         return;
442     }
443
444     /**
445      * {@inheritDoc}
446      *
447      * @param family {@inheritDoc}
448      * @throws HtmlParseException {@inheritDoc}
449      */
450     @Override
451     public void startSysEvent(EventFamily family)
452             throws HtmlParseException{
453         resetEventContext();
454
455         this.eventFamily = family;
456         this.eventContent = new DecodedContent();
457
458         return;
459     }
460
461     /**
462      * {@inheritDoc}
463      *
464      * @param type {@inheritDoc}
465      * @throws HtmlParseException {@inheritDoc}
466      */
467     @Override
468     public void sysEventType(SysEventType type)
469             throws HtmlParseException{
470         this.sysEventType = type;
471         return;
472     }
473
474     /**
475      * {@inheritDoc}
476      *
477      * <p>イベント文字列中の文字実体参照は展開される。
478      *
479      * @param content {@inheritDoc}
480      * @param contentRange {@inheritDoc}
481      * @throws HtmlParseException {@inheritDoc}
482      */
483     @Override
484     public void sysEventContent(DecodedContent content,
485                                 SeqRange contentRange)
486             throws HtmlParseException{
487         this.converter.append(this.eventContent, content, contentRange);
488         return;
489     }
490
491     /**
492      * {@inheritDoc}
493      *
494      * <p>イベント文内Aタグ内容の文字実体参照は展開される。
495      * HREF属性値は無視される
496      *
497      * @param content {@inheritDoc}
498      * @param anchorRange {@inheritDoc}
499      * @param contentRange {@inheritDoc}
500      * @throws HtmlParseException {@inheritDoc}
501      */
502     @Override
503     public void sysEventContentAnchor(DecodedContent content,
504                                       SeqRange anchorRange,
505                                       SeqRange contentRange)
506             throws HtmlParseException{
507         this.converter.append(this.eventContent, content, contentRange);
508         return;
509     }
510
511     /**
512      * {@inheritDoc}
513      *
514      * @throws HtmlParseException {@inheritDoc}
515      */
516     @Override
517     public void sysEventContentBreak() throws HtmlParseException{
518         this.eventContent.append('\n');
519         return;
520     }
521
522     /**
523      * {@inheritDoc}
524      *
525      * <p>Avatarリストの先頭にAvatarが、
526      * intリストの先頭にエントリー番号が入る。
527      *
528      * @param content {@inheritDoc}
529      * @param entryNo {@inheritDoc}
530      * @param avatarRange {@inheritDoc}
531      * @throws HtmlParseException {@inheritDoc}
532      */
533     @Override
534     public void sysEventOnStage(DecodedContent content,
535                                 int entryNo,
536                                 SeqRange avatarRange)
537             throws HtmlParseException{
538         Avatar newAvatar = toAvatar(content, avatarRange);
539         this.integerList.add(entryNo);
540         this.avatarList.add(newAvatar);
541         return;
542     }
543
544     /**
545      * {@inheritDoc}
546      *
547      * <p>役職者数開示に伴い役職リストとintリストに一件ずつ追加される。
548      *
549      * @param role {@inheritDoc}
550      * @param num {@inheritDoc}
551      * @throws HtmlParseException {@inheritDoc}
552      */
553     @Override
554     public void sysEventOpenRole(GameRole role, int num)
555             throws HtmlParseException{
556         this.roleList.add(role);
557         this.integerList.add(num);
558         return;
559     }
560
561     /**
562      * {@inheritDoc}
563      *
564      * <p>噛み及びハム溶けに伴いAvatarリストに1件ずつ追加される。
565      *
566      * @param content {@inheritDoc}
567      * @param avatarRange {@inheritDoc}
568      * @throws HtmlParseException {@inheritDoc}
569      */
570     @Override
571     public void sysEventMurdered(DecodedContent content,
572                                  SeqRange avatarRange)
573             throws HtmlParseException{
574         Avatar murdered = toAvatar(content, avatarRange);
575         this.avatarList.add(murdered);
576         return;
577     }
578
579     /**
580      * {@inheritDoc}
581      *
582      * <p>生存者表示に伴いAvatarリストに1件ずつ追加される。
583      *
584      * @param content {@inheritDoc}
585      * @param avatarRange {@inheritDoc}
586      * @throws HtmlParseException {@inheritDoc}
587      */
588     @Override
589     public void sysEventSurvivor(DecodedContent content,
590                                  SeqRange avatarRange)
591             throws HtmlParseException{
592         Avatar survivor = toAvatar(content, avatarRange);
593         this.avatarList.add(survivor);
594         return;
595     }
596
597     /**
598      * {@inheritDoc}
599      *
600      * <p>G国以外での処刑に伴い、
601      * 投票元と投票先の順でAvatarリストに追加される。
602      *
603      * <p>被処刑者がいればAvatarリストの最後に追加される。
604      *
605      * @param content {@inheritDoc}
606      * @param voteByRange {@inheritDoc}
607      * @param voteToRange {@inheritDoc}
608      * @throws HtmlParseException {@inheritDoc}
609      */
610     @Override
611     public void sysEventCounting(DecodedContent content,
612                                  SeqRange voteByRange,
613                                  SeqRange voteToRange)
614             throws HtmlParseException{
615         if(voteByRange.isValid()){
616             Avatar voteBy = toAvatar(content, voteByRange);
617             this.avatarList.add(voteBy);
618         }
619         Avatar voteTo = toAvatar(content, voteToRange);
620         this.avatarList.add(voteTo);
621         return;
622     }
623
624     /**
625      * {@inheritDoc}
626      *
627      * <p>G国処刑に伴い、
628      * 投票元と投票先の順でAvatarリストに追加される。
629      *
630      * @param content {@inheritDoc}
631      * @param voteByRange {@inheritDoc}
632      * @param voteToRange {@inheritDoc}
633      * @throws HtmlParseException {@inheritDoc}
634      */
635     @Override
636     public void sysEventCounting2(DecodedContent content,
637                                   SeqRange voteByRange,
638                                   SeqRange voteToRange)
639             throws HtmlParseException{
640         sysEventCounting(content, voteByRange, voteToRange);
641         return;
642     }
643
644     /**
645      * {@inheritDoc}
646      *
647      * <p>Avatarリストの先頭に突然死者が入る。
648      *
649      * @param content {@inheritDoc}
650      * @param avatarRange {@inheritDoc}
651      * @throws HtmlParseException {@inheritDoc}
652      */
653     @Override
654     public void sysEventSuddenDeath(DecodedContent content,
655                                     SeqRange avatarRange)
656             throws HtmlParseException{
657         Avatar suddenDeath = toAvatar(content, avatarRange);
658         this.avatarList.add(suddenDeath);
659         return;
660     }
661
662     /**
663      * {@inheritDoc}
664      *
665      * <p>プレイヤー情報開示に伴い、
666      * Avatarリストに1件、
667      * 文字列リストにURLとプレイヤー名の2件、
668      * intリストに生死(1or0)が1件、
669      * Roleリストに役職が1件追加される。
670      *
671      * @param content {@inheritDoc}
672      * @param avatarRange {@inheritDoc}
673      * @param anchorRange {@inheritDoc}
674      * @param loginRange {@inheritDoc}
675      * @param isLiving {@inheritDoc}
676      * @param role {@inheritDoc}
677      * @throws HtmlParseException {@inheritDoc}
678      */
679     @Override
680     public void sysEventPlayerList(DecodedContent content,
681                                    SeqRange avatarRange,
682                                    SeqRange anchorRange,
683                                    SeqRange loginRange,
684                                    boolean isLiving,
685                                    GameRole role )
686             throws HtmlParseException{
687         Avatar who = toAvatar(content, avatarRange);
688
689         CharSequence anchor;
690         if(anchorRange.isValid()){
691             anchor = this.converter.convert(content, anchorRange);
692         }else{
693             anchor = "";
694         }
695         CharSequence account = this.converter
696                                    .convert(content, loginRange);
697
698         Integer liveOrDead;
699         if(isLiving) liveOrDead = 1;
700         else         liveOrDead = 0;
701
702         this.avatarList.add(who);
703         this.charseqList.add(anchor);
704         this.charseqList.add(account);
705         this.integerList.add(liveOrDead);
706         this.roleList.add(role);
707
708         return;
709     }
710
711     /**
712      * {@inheritDoc}
713      *
714      * <p>G国処刑に伴い、Avatarリストに投票先が1件、
715      * intリストに得票数が1件追加される。
716      * 最後に被処刑者がAvatarリストに1件、負の値がintリストに1件追加される。
717      *
718      * @param content {@inheritDoc}
719      * @param avatarRange {@inheritDoc}
720      * @param votes {@inheritDoc}
721      * @throws HtmlParseException {@inheritDoc}
722      */
723     @Override
724     public void sysEventExecution(DecodedContent content,
725                                   SeqRange avatarRange,
726                                   int votes )
727             throws HtmlParseException{
728         Avatar who = toAvatar(content, avatarRange);
729
730         this.avatarList.add(who);
731         this.integerList.add(votes);
732
733         return;
734     }
735
736     /**
737      * {@inheritDoc}
738      *
739      * <p>エントリー促しに伴い、
740      * intリストに分数、最小メンバ数、最大メンバ数の3件が設定される。
741      *
742      * @param hour {@inheritDoc}
743      * @param minute {@inheritDoc}
744      * @param minLimit {@inheritDoc}
745      * @param maxLimit {@inheritDoc}
746      * @throws HtmlParseException {@inheritDoc}
747      */
748     @Override
749     public void sysEventAskEntry(int hour, int minute,
750                                  int minLimit, int maxLimit)
751             throws HtmlParseException{
752         this.integerList.add(hour * 60 + minute);
753         this.integerList.add(minLimit);
754         this.integerList.add(maxLimit);
755         return;
756     }
757
758     /**
759      * {@inheritDoc}
760      *
761      * <p>エントリー完了に伴い、分数をintリストに設定する。
762      *
763      * @param hour {@inheritDoc}
764      * @param minute {@inheritDoc}
765      * @throws HtmlParseException {@inheritDoc}
766      */
767     @Override
768     public void sysEventAskCommit(int hour, int minute)
769             throws HtmlParseException{
770         this.integerList.add(hour * 60 + minute);
771         return;
772     }
773
774     /**
775      * {@inheritDoc}
776      *
777      * <p>未発言者一覧に伴い、
778      * 未発言者はAvatarリストへ1件ずつ追加される。
779      *
780      * @param content {@inheritDoc}
781      * @param avatarRange {@inheritDoc}
782      * @throws HtmlParseException {@inheritDoc}
783      */
784     @Override
785     public void sysEventNoComment(DecodedContent content,
786                                   SeqRange avatarRange)
787             throws HtmlParseException{
788         Avatar noComAvatar = toAvatar(content, avatarRange);
789         this.avatarList.add(noComAvatar);
790         return;
791     }
792
793     /**
794      * {@inheritDoc}
795      *
796      * <p>決着発表に伴い、
797      * Roleリストに勝者が1件、intリスト分数が1件設定される。
798      *
799      * <p>村勝利の場合は素村役職が用いられる。
800      *
801      * @param winner {@inheritDoc}
802      * @param hour {@inheritDoc}
803      * @param minute {@inheritDoc}
804      * @throws HtmlParseException {@inheritDoc}
805      */
806     @Override
807     public void sysEventStayEpilogue(Team winner, int hour, int minute)
808             throws HtmlParseException{
809         GameRole role = null;
810
811         switch(winner){
812         case VILLAGE: role = GameRole.INNOCENT; break;
813         case WOLF:    role = GameRole.WOLF;     break;
814         case HAMSTER: role = GameRole.HAMSTER;  break;
815         default: assert false; break;
816         }
817
818         this.roleList.add(role);
819         this.integerList.add(hour * 60 + minute);
820
821         return;
822     }
823
824     /**
825      * {@inheritDoc}
826      *
827      * <p>護衛に伴い、Avatarリストに護衛元1件と護衛先1件が設定される。
828      *
829      * @param content {@inheritDoc}
830      * @param guardByRange {@inheritDoc}
831      * @param guardToRange {@inheritDoc}
832      * @throws HtmlParseException {@inheritDoc}
833      */
834     @Override
835     public void sysEventGuard(DecodedContent content,
836                               SeqRange guardByRange,
837                               SeqRange guardToRange)
838             throws HtmlParseException{
839         Avatar guardBy = toAvatar(content, guardByRange);
840         Avatar guardTo = toAvatar(content, guardToRange);
841         this.avatarList.add(guardBy);
842         this.avatarList.add(guardTo);
843         return;
844     }
845
846     /**
847      * {@inheritDoc}
848      *
849      * <p>占いに伴い、
850      * 占い元が1件、占い先が1件Avatarリストに設定される。
851      *
852      * @param content {@inheritDoc}
853      * @param judgeByRange {@inheritDoc}
854      * @param judgeToRange {@inheritDoc}
855      * @throws HtmlParseException {@inheritDoc}
856      */
857     @Override
858     public void sysEventJudge(DecodedContent content,
859                               SeqRange judgeByRange,
860                               SeqRange judgeToRange)
861             throws HtmlParseException{
862         Avatar judgeBy = toAvatar(content, judgeByRange);
863         Avatar judgeTo = toAvatar(content, judgeToRange);
864         this.avatarList.add(judgeBy);
865         this.avatarList.add(judgeTo);
866         return;
867     }
868
869     /**
870      * {@inheritDoc}
871      *
872      * <p>パースの完了した1件のイベントインスタンスを
873      * Periodに追加する。
874      *
875      * <p>襲撃もしくは襲撃なしのイベントの前に、
876      * 「今日がお前の命日だ!」で終わる赤ログが出現した場合、
877      * 赤カウントに含めない。
878      *
879      * @throws HtmlParseException {@inheritDoc}
880      */
881     @Override
882     public void endSysEvent() throws HtmlParseException{
883         SysEvent event = new SysEvent();
884         event.setEventFamily(this.eventFamily);
885         event.setSysEventType(this.sysEventType);
886         event.setContent(this.eventContent);
887         event.addAvatarList(this.avatarList);
888         event.addRoleList(this.roleList);
889         event.addIntegerList(this.integerList);
890         event.addCharSequenceList(this.charseqList);
891
892         this.period.addTopic(event);
893
894         boolean isMurderResult =
895                    this.sysEventType == SysEventType.MURDERED
896                 || this.sysEventType == SysEventType.NOMURDER;
897
898         if(isMurderResult){
899             for(Topic topic : this.period.getTopicList()){
900                 if( ! (topic instanceof Talk) ) continue;
901                 Talk talk = (Talk) topic;
902                 if(talk.isMurderNotice()){
903                     talk.setCount(-1);
904                     this.countMap.clear();
905                     break;
906                 }
907             }
908         }
909
910         resetEventContext();
911
912         return;
913     }
914
915     /**
916      * {@inheritDoc}
917      *
918      * @throws HtmlParseException {@inheritDoc}
919      */
920     @Override
921     public void endParse() throws HtmlParseException{
922         reset();
923         return;
924     }
925
926 }