OSDN Git Service

ダイヤモンド演算子対応
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / data / Village.java
1 /*
2  * Village
3  *
4  * License : The MIT License
5  * Copyright(c) 2008 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.data;
9
10 import java.awt.image.BufferedImage;
11 import java.io.IOException;
12 import java.text.MessageFormat;
13 import java.util.Collections;
14 import java.util.Comparator;
15 import java.util.HashMap;
16 import java.util.LinkedList;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.logging.Level;
20 import java.util.logging.Logger;
21 import jp.sfjp.jindolf.net.HtmlSequence;
22 import jp.sfjp.jindolf.net.ServerAccess;
23 import jp.sfjp.jindolf.util.GUIUtils;
24 import jp.sourceforge.jindolf.corelib.LandDef;
25 import jp.sourceforge.jindolf.corelib.LandState;
26 import jp.sourceforge.jindolf.corelib.PeriodType;
27 import jp.sourceforge.jindolf.corelib.VillageState;
28 import jp.sourceforge.jindolf.parser.DecodedContent;
29 import jp.sourceforge.jindolf.parser.HtmlAdapter;
30 import jp.sourceforge.jindolf.parser.HtmlParseException;
31 import jp.sourceforge.jindolf.parser.HtmlParser;
32 import jp.sourceforge.jindolf.parser.PageType;
33 import jp.sourceforge.jindolf.parser.SeqRange;
34
35 /**
36  * いわゆる「村」。
37  */
38 public class Village implements Comparable<Village> {
39
40     /**
41      * 村同士を比較するためのComparator。
42      */
43     private static class VillageComparator implements Comparator<Village> {
44
45         /**
46          * コンストラクタ。
47          */
48         public VillageComparator(){
49             super();
50             return;
51         }
52
53         /**
54          * {@inheritDoc}
55          * @param v1 {@inheritDoc}
56          * @param v2 {@inheritDoc}
57          * @return {@inheritDoc}
58          */
59         @Override
60         public int compare(Village v1, Village v2){
61             int v1Num;
62             if(v1 == null) v1Num = Integer.MIN_VALUE;
63             else           v1Num = v1.getVillageIDNum();
64
65             int v2Num;
66             if(v2 == null) v2Num = Integer.MIN_VALUE;
67             else           v2Num = v2.getVillageIDNum();
68
69             return v1Num - v2Num;
70         }
71
72     }
73
74     private static final int GID_MIN = 3;
75
76     private static final Comparator<Village> VILLAGE_COMPARATOR =
77             new VillageComparator();
78
79     private static final HtmlParser PARSER = new HtmlParser();
80     private static final VillageHeadHandler HANDLER =
81             new VillageHeadHandler();
82
83     private static final Logger LOGGER = Logger.getAnonymousLogger();
84
85     static{
86         PARSER.setBasicHandler   (HANDLER);
87         PARSER.setSysEventHandler(HANDLER);
88         PARSER.setTalkHandler    (HANDLER);
89     }
90
91
92     private final Land parentLand;
93     private final String villageID;
94     private final int villageIDNum;
95     private final String villageName;
96
97     private final boolean isValid;
98
99     private int limitMonth;
100     private int limitDay;
101     private int limitHour;
102     private int limitMinute;
103
104     private VillageState state = VillageState.UNKNOWN;
105
106     private final LinkedList<Period> periodList = new LinkedList<>();
107     private final List<Period> unmodList =
108             Collections.unmodifiableList(this.periodList);
109
110     private final Map<String, Avatar> avatarMap =
111             new HashMap<>();
112
113     private final Map<Avatar, BufferedImage> faceImageMap =
114             new HashMap<>();
115     private final Map<Avatar, BufferedImage> bodyImageMap =
116             new HashMap<>();
117     private final Map<Avatar, BufferedImage> faceMonoImageMap =
118             new HashMap<>();
119     private final Map<Avatar, BufferedImage> bodyMonoImageMap =
120             new HashMap<>();
121
122
123     /**
124      * Villageを生成する。
125      * @param parentLand Villageの所属する国
126      * @param villageID 村のID
127      * @param villageName 村の名前
128      */
129     public Village(Land parentLand, String villageID, String villageName) {
130         this.parentLand    = parentLand;
131         this.villageID   = villageID.intern();
132         this.villageIDNum = Integer.parseInt(this.villageID);
133         this.villageName = villageName.intern();
134
135         this.isValid = this.parentLand.getLandDef()
136                        .isValidVillageId(this.villageIDNum);
137
138         return;
139     }
140
141
142     /**
143      * 村同士を比較するためのComparatorを返す。
144      * @return Comparatorインスタンス
145      */
146     public static Comparator<Village> comparator(){
147         return VILLAGE_COMPARATOR;
148     }
149
150     /**
151      * 人狼BBSサーバからPeriod一覧情報が含まれたHTMLを取得し、
152      * Periodリストを更新する。
153      * @param village 村
154      * @throws java.io.IOException ネットワーク入出力の異常
155      */
156     public static synchronized void updateVillage(Village village)
157             throws IOException{
158         Land land = village.getParentLand();
159         LandDef landDef = land.getLandDef();
160         LandState landState = landDef.getLandState();
161         ServerAccess server = land.getServerAccess();
162
163         HtmlSequence html;
164         if(landState == LandState.ACTIVE){
165             html = server.getHTMLBoneHead(village);
166         }else{
167             html = server.getHTMLVillage(village);
168         }
169
170         DecodedContent content = html.getContent();
171         HANDLER.setVillage(village);
172         try{
173             PARSER.parseAutomatic(content);
174         }catch(HtmlParseException e){
175             LOGGER.log(Level.WARNING, "村の状態が不明", e);
176         }
177
178         return;
179     }
180
181     /**
182      * 所属する国を返す。
183      * @return 村の所属する国(Land)
184      */
185     public Land getParentLand(){
186         return this.parentLand;
187     }
188
189     /**
190      * 村のID文字列を返す。
191      * @return 村ID
192      */
193     public String getVillageID(){
194         return this.villageID;
195     }
196
197     /**
198      * 村のID数値を返す。
199      * @return 村ID
200      */
201     public int getVillageIDNum(){
202         return this.villageIDNum;
203     }
204
205     /**
206      * 村の名前を返す。
207      * @return 村の名前
208      */
209     public String getVillageName(){
210         StringBuilder name = new StringBuilder();
211
212         LandDef landDef = this.parentLand.getLandDef();
213         String prefix = landDef.getLandPrefix();
214         name.append(prefix);
215
216         StringBuilder id = new StringBuilder(this.villageID);
217         if(landDef.getLandId().equals("wolfg")){
218             while(id.length() < GID_MIN){
219                 id.insert(0, '0');
220             }
221         }
222         name.append(id);
223
224         String result = name.toString();
225         return result;
226     }
227
228     /**
229      * 村の長い名前を返す。
230      * @return 村の長い名前
231      */
232     public String getVillageFullName(){
233         return this.villageName;
234     }
235
236     /**
237      * 村の状態を返す。
238      * @return 村の状態
239      */
240     public VillageState getState(){
241         return this.state;
242     }
243
244     /**
245      * 村の状態を設定する。
246      * @param state 村の状態
247      */
248     public void setState(VillageState state){
249         this.state = state;
250         return;
251     }
252
253     /**
254      * プロローグを返す。
255      * @return プロローグ
256      */
257     public Period getPrologue(){
258         for(Period period : this.periodList){
259             if(period.isPrologue()) return period;
260         }
261         return null;
262     }
263
264     /**
265      * エピローグを返す。
266      * @return エピローグ
267      */
268     public Period getEpilogue(){
269         for(Period period : this.periodList){
270             if(period.isEpilogue()) return period;
271         }
272         return null;
273     }
274
275     /**
276      * 指定された日付の進行日を返す。
277      * @param day 日付
278      * @return Period
279      */
280     public Period getProgress(int day){
281         for(Period period : this.periodList){
282             if(   period.isProgress()
283                && period.getDay() == day ) return period;
284         }
285         return null;
286     }
287
288     /**
289      * PROGRESS状態のPeriodの総数を返す。
290      * @return PROGRESS状態のPeriod総数
291      */
292     public int getProgressDays(){
293         int result = 0;
294         for(Period period : this.periodList){
295             if(period.isProgress()) result++;
296         }
297         return result;
298     }
299
300     /**
301      * 指定されたPeriodインデックスのPeriodを返す。
302      * プロローグやエピローグへのアクセスも可能。
303      * @param day Periodインデックス
304      * @return Period
305      */
306     public Period getPeriod(int day){
307         return this.periodList.get(day);
308     }
309
310     /**
311      * 指定されたアンカーの対象のPeriodを返す。
312      * @param anchor アンカー
313      * @return Period
314      */
315     public Period getPeriod(Anchor anchor){
316         Period anchorPeriod;
317
318         if(anchor.isEpilogueDay()){
319             anchorPeriod = getEpilogue();
320             return anchorPeriod;
321         }
322
323         int anchorDay = anchor.getDay();
324         anchorPeriod = getPeriod(anchorDay);
325
326         return anchorPeriod;
327     }
328
329     /**
330      * Period総数を返す。
331      * @return Period総数
332      */
333     public int getPeriodSize(){
334         return this.periodList.size();
335     }
336
337     /**
338      * Periodへのリストを返す。
339      * @return Periodのリスト。
340      */
341     public List<Period> getPeriodList(){
342         return this.unmodList;
343     }
344
345     /**
346      * 指定した名前で村に登録されているAvatarを返す。
347      * @param fullName Avatarの名前
348      * @return Avatar
349      */
350     public Avatar getAvatar(String fullName){
351         // TODO CharSequenceにできない?
352         Avatar avatar;
353
354         avatar = Avatar.getPredefinedAvatar(fullName);
355         if( avatar != null ){
356             preloadAvatarFace(avatar);
357             return avatar;
358         }
359
360         avatar = this.avatarMap.get(fullName);
361         if( avatar != null ){
362             preloadAvatarFace(avatar);
363             return avatar;
364         }
365
366         return null;
367     }
368
369     /**
370      * Avatarの顔画像を事前にロードする。
371      * @param avatar Avatar
372      */
373     private void preloadAvatarFace(Avatar avatar){
374         if(this.faceImageMap.get(avatar) != null) return;
375
376         Land land = getParentLand();
377         LandDef landDef = land.getLandDef();
378
379         String template = landDef.getFaceURITemplate();
380         int serialNo = avatar.getIdNum();
381         String uri = MessageFormat.format(template, serialNo);
382
383         BufferedImage image = land.downloadImage(uri);
384         if(image == null) image = GUIUtils.getNoImage();
385
386         this.faceImageMap.put(avatar, image);
387
388         return;
389     }
390
391     /**
392      * Avatarを村に登録する。
393      * @param avatar Avatar
394      */
395     // 未知のAvatar出現時の処理が不完全
396     public void addAvatar(Avatar avatar){
397         if(avatar == null) return;
398         String fullName = avatar.getFullName();
399         this.avatarMap.put(fullName, avatar);
400
401         preloadAvatarFace(avatar);
402
403         return;
404     }
405
406     /**
407      * 村に登録されたAvatarの顔イメージを返す。
408      * @param avatar Avatar
409      * @return 顔イメージ
410      */
411     // TODO 失敗したらプロローグを強制読み込みして再トライしたい
412     public BufferedImage getAvatarFaceImage(Avatar avatar){
413         return this.faceImageMap.get(avatar);
414     }
415
416     /**
417      * 村に登録されたAvatarの全身像イメージを返す。
418      * @param avatar Avatar
419      * @return 全身イメージ
420      */
421     public BufferedImage getAvatarBodyImage(Avatar avatar){
422         BufferedImage result;
423         result = this.bodyImageMap.get(avatar);
424         if(result != null) return result;
425
426         Land land = getParentLand();
427         LandDef landDef = land.getLandDef();
428
429         String template = landDef.getBodyURITemplate();
430         int serialNo = avatar.getIdNum();
431         String uri = MessageFormat.format(template, serialNo);
432
433         result = land.downloadImage(uri);
434         if(result == null) result = GUIUtils.getNoImage();
435
436         this.bodyImageMap.put(avatar, result);
437
438         return result;
439     }
440
441     /**
442      * 村に登録されたAvatarのモノクロ顔イメージを返す。
443      * @param avatar Avatar
444      * @return 顔イメージ
445      */
446     public BufferedImage getAvatarFaceMonoImage(Avatar avatar){
447         BufferedImage result;
448         result = this.faceMonoImageMap.get(avatar);
449         if(result == null){
450             result = getAvatarFaceImage(avatar);
451             result = GUIUtils.createMonoImage(result);
452             this.faceMonoImageMap.put(avatar, result);
453         }
454         return result;
455     }
456
457     /**
458      * 村に登録されたAvatarの全身像イメージを返す。
459      * @param avatar Avatar
460      * @return 全身イメージ
461      */
462     public BufferedImage getAvatarBodyMonoImage(Avatar avatar){
463         BufferedImage result;
464         result = this.bodyMonoImageMap.get(avatar);
465         if(result == null){
466             result = getAvatarBodyImage(avatar);
467             result = GUIUtils.createMonoImage(result);
468             this.bodyMonoImageMap.put(avatar, result);
469         }
470         return result;
471     }
472
473     /**
474      * 国に登録された墓イメージを返す。
475      * @return 墓イメージ
476      */
477     public BufferedImage getGraveImage(){
478         BufferedImage result = getParentLand().getGraveIconImage();
479         return result;
480     }
481
482     /**
483      * 国に登録された墓イメージ(大)を返す。
484      * @return 墓イメージ(大)
485      */
486     public BufferedImage getGraveBodyImage(){
487         BufferedImage result = getParentLand().getGraveBodyImage();
488         return result;
489     }
490
491     /**
492      * 村にアクセスするためのCGIクエリーを返す。
493      * @return CGIクエリー
494      */
495     public CharSequence getCGIQuery(){
496         StringBuilder result = new StringBuilder();
497         result.append("?vid=").append(getVillageID());
498         return result;
499     }
500
501     /**
502      * 次回更新月を返す。
503      * @return 更新月(1-12)
504      */
505     public int getLimitMonth(){
506         return this.limitMonth;
507     }
508
509     /**
510      * 次回更新日を返す。
511      * @return 更新日(1-31)
512      */
513     public int getLimitDay(){
514         return this.limitDay;
515     }
516
517     /**
518      * 次回更新時を返す。
519      * @return 更新時(0-23)
520      */
521     public int getLimitHour(){
522         return this.limitHour;
523     }
524
525     /**
526      * 次回更新分を返す。
527      * @return 更新分(0-59)
528      */
529     public int getLimitMinute(){
530         return this.limitMinute;
531     }
532
533     /**
534      * 有効な村か否か判定する。
535      * @return 無効な村ならfalse
536      */
537     public boolean isValid(){
538         return this.isValid;
539     }
540
541     /**
542      * Periodリストの指定したインデックスにPeriodを上書きする。
543      * リストのサイズと同じインデックスを指定する事が許される。
544      * その場合の動作はList.addと同じ。
545      * @param index Periodリストのインデックス。
546      * @param period 上書きするPeriod
547      * @throws java.lang.IndexOutOfBoundsException インデックスの指定がおかしい
548      */
549     private void setPeriod(int index, Period period)
550             throws IndexOutOfBoundsException{
551         int listSize = this.periodList.size();
552         if(index == listSize){
553             this.periodList.add(period);
554         }else if(index < listSize){
555             this.periodList.set(index, period);
556         }else{
557             throw new IndexOutOfBoundsException();
558         }
559         return;
560     }
561
562     /**
563      * アンカーに一致する会話(Talk)のリストを取得する。
564      * @param anchor アンカー
565      * @return Talkのリスト
566      * @throws java.io.IOException おそらくネットワークエラー
567      */
568     public List<Talk> getTalkListFromAnchor(Anchor anchor)
569             throws IOException{
570         List<Talk> result = new LinkedList<>();
571
572         /* G国アンカー対応 */
573         if(anchor.hasTalkNo()){
574             // 事前に全Periodがロードされているのが前提
575             for(Period period : this.periodList){
576                 Talk talk = period.getNumberedTalk(anchor.getTalkNo());
577                 if(talk == null) continue;
578                 result.add(talk);
579             }
580             return result;
581         }
582
583         Period anchorPeriod = getPeriod(anchor);
584         if(anchorPeriod == null) return result;
585
586         Period.parsePeriod(anchorPeriod, false);
587
588         for(Topic topic : anchorPeriod.getTopicList()){
589             if( ! (topic instanceof Talk) ) continue;
590             Talk talk = (Talk) topic;
591             if(talk.getHour()   != anchor.getHour()  ) continue;
592             if(talk.getMinute() != anchor.getMinute()) continue;
593             result.add(talk);
594         }
595         return result;
596     }
597
598     /**
599      * 全Periodの発言データをアンロードする。
600      */
601     public void unloadPeriods(){
602         for(Period period : this.periodList){
603             period.unload();
604         }
605         return;
606     }
607
608     /**
609      * {@inheritDoc}
610      * 二つの村を順序付ける。
611      * @param village {@inheritDoc}
612      * @return {@inheritDoc}
613      */
614     @Override
615     public int compareTo(Village village){
616         int cmpResult = VILLAGE_COMPARATOR.compare(this, village);
617         return cmpResult;
618     }
619
620     /**
621      * {@inheritDoc}
622      * 二つの村が等しいか調べる。
623      * @param obj {@inheritDoc}
624      * @return {@inheritDoc}
625      */
626     @Override
627     public boolean equals(Object obj){
628         if(obj == null) return false;
629         if( ! (obj instanceof Village) ) return false;
630         Village village = (Village) obj;
631
632         if( getParentLand() != village.getParentLand() ) return false;
633
634         int cmpResult = compareTo(village);
635         if(cmpResult == 0) return true;
636         return false;
637     }
638
639     /**
640      * {@inheritDoc}
641      * @return {@inheritDoc}
642      */
643     @Override
644     public int hashCode(){
645         int homeHash = getParentLand().hashCode();
646         int vidHash = getVillageID().hashCode();
647         int result = homeHash ^ vidHash;
648         return result;
649     }
650
651     /**
652      * {@inheritDoc}
653      * 村の文字列表現を返す。
654      * 村の名前と等しい。
655      * @return 村の名前
656      */
657     @Override
658     public String toString(){
659         return getVillageFullName();
660     }
661
662     /**
663      * Period一覧取得用ハンドラ。
664      */
665     private static class VillageHeadHandler extends HtmlAdapter{
666
667         private Village village = null;
668
669         private boolean hasPrologue;
670         private boolean hasProgress;
671         private boolean hasEpilogue;
672         private boolean hasDone;
673         private int maxProgress;
674
675         /**
676          * コンストラクタ。
677          */
678         public VillageHeadHandler(){
679             super();
680             return;
681         }
682
683         /**
684          * 更新対象の村を設定する。
685          * @param village 村
686          */
687         public void setVillage(Village village){
688             this.village = village;
689             return;
690         }
691
692         /**
693          * リセットを行う。
694          */
695         public void reset(){
696             this.hasPrologue = false;
697             this.hasProgress = false;
698             this.hasEpilogue = false;
699             this.hasDone = false;
700             this.maxProgress = 0;
701             return;
702         }
703
704         /**
705          * パース結果から村の状態を算出する。
706          * @return 村の状態
707          */
708         public VillageState getVillageState(){
709             if(this.hasDone){
710                 return VillageState.GAMEOVER;
711             }else if(this.hasEpilogue){
712                 return VillageState.EPILOGUE;
713             }else if(this.hasProgress){
714                 return VillageState.PROGRESS;
715             }else if(this.hasPrologue){
716                 return VillageState.PROLOGUE;
717             }
718
719             return VillageState.UNKNOWN;
720         }
721
722         /**
723          * {@inheritDoc}
724          * @param content {@inheritDoc}
725          * @throws HtmlParseException {@inheritDoc}
726          */
727         @Override
728         public void startParse(DecodedContent content)
729                 throws HtmlParseException{
730             reset();
731             return;
732         }
733
734         /**
735          * {@inheritDoc}
736          * 自動判定の結果が日ページでなければ例外を投げる。
737          * @param type {@inheritDoc}
738          * @throws HtmlParseException {@inheritDoc} 意図しないページが来た。
739          */
740         @Override
741         public void pageType(PageType type) throws HtmlParseException{
742             if(type != PageType.PERIOD_PAGE){
743                 throw new HtmlParseException(
744                         "日ページが必要です。");
745             }
746             return;
747         }
748
749         /**
750          * {@inheritDoc}
751          * @param month {@inheritDoc}
752          * @param day {@inheritDoc}
753          * @param hour {@inheritDoc}
754          * @param minute {@inheritDoc}
755          * @throws HtmlParseException {@inheritDoc}
756          */
757         @Override
758         public void commitTime(int month, int day,
759                                 int hour, int minute)
760                 throws HtmlParseException{
761             this.village.limitMonth  = month;
762             this.village.limitDay    = day;
763             this.village.limitHour   = hour;
764             this.village.limitMinute = minute;
765
766             return;
767         }
768
769         /**
770          * {@inheritDoc}
771          * @param content {@inheritDoc}
772          * @param anchorRange {@inheritDoc}
773          * @param periodType {@inheritDoc}
774          * @param day {@inheritDoc}
775          * @throws HtmlParseException {@inheritDoc}
776          */
777         @Override
778         public void periodLink(DecodedContent content,
779                                 SeqRange anchorRange,
780                                 PeriodType periodType,
781                                 int day)
782                 throws HtmlParseException{
783             if(periodType == null){
784                 this.hasDone = true;
785                 return;
786             }
787
788             switch(periodType){
789             case PROLOGUE:
790                 this.hasPrologue = true;
791                 break;
792             case PROGRESS:
793                 this.hasProgress = true;
794                 this.maxProgress = day;
795                 break;
796             case EPILOGUE:
797                 this.hasEpilogue = true;
798                 break;
799             default:
800                 assert false;
801                 break;
802             }
803
804             return;
805         }
806
807         /**
808          * {@inheritDoc}
809          * @throws HtmlParseException {@inheritDoc}
810          */
811         @Override
812         public void endParse() throws HtmlParseException{
813             Land land = this.village.getParentLand();
814             LandDef landDef = land.getLandDef();
815             LandState landState = landDef.getLandState();
816
817             VillageState villageState = getVillageState();
818             if(villageState == VillageState.UNKNOWN){
819                 this.village.setState(villageState);
820                 this.village.periodList.clear();
821                 LOGGER.warning("村の状況を読み取れません");
822                 return;
823             }
824
825             if(landState == LandState.ACTIVE){
826                 this.village.setState(villageState);
827             }else{
828                 this.village.setState(VillageState.GAMEOVER);
829             }
830
831             modifyPeriodList();
832
833             return;
834         }
835
836         /**
837          * 抽出したリンク情報に伴いPeriodリストを更新する。
838          * まだPeriodデータのロードは行われない。
839          * ゲーム進行中の村で更新時刻をまたいで更新が行われた場合、
840          * 既存のPeriodリストが伸張する場合がある。
841          */
842         private void modifyPeriodList(){
843             Period lastPeriod = null;
844
845             if(this.hasPrologue){
846                 Period prologue = this.village.getPrologue();
847                 if(prologue == null){
848                     lastPeriod = new Period(this.village,
849                                             PeriodType.PROLOGUE, 0);
850                     this.village.setPeriod(0, lastPeriod);
851                 }else{
852                     lastPeriod = prologue;
853                 }
854             }
855
856             if(this.hasProgress){
857                 for(int day = 1; day <= this.maxProgress; day++){
858                     Period progress = this.village.getProgress(day);
859                     if(progress == null){
860                         lastPeriod = new Period(this.village,
861                                                 PeriodType.PROGRESS, day);
862                         this.village.setPeriod(day, lastPeriod);
863                     }else{
864                         lastPeriod = progress;
865                     }
866                 }
867             }
868
869             if(this.hasEpilogue){
870                 Period epilogue = this.village.getEpilogue();
871                 if(epilogue == null){
872                     lastPeriod = new Period(this.village,
873                                             PeriodType.EPILOGUE,
874                                             this.maxProgress +1);
875                     this.village.setPeriod(this.maxProgress +1, lastPeriod);
876                 }else{
877                     lastPeriod = epilogue;
878                 }
879             }
880
881             assert this.village.getPeriodSize() > 0;
882             assert lastPeriod != null;
883
884             // 念のためチョップ。
885             // リロードで村が縮むわけないじゃん。みんな大げさだなあ
886             while(this.village.periodList.getLast() != lastPeriod){
887                 this.village.periodList.removeLast();
888             }
889
890             if(this.village.getState() != VillageState.GAMEOVER){
891                 lastPeriod.setHot(true);
892             }
893
894             return;
895         }
896
897     }
898
899 }