OSDN Git Service

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