OSDN Git Service

10759b3f03ee54153fc8e3c75a0828fa5bf57c14
[jindolf/JinCore.git] / src / main / java / jp / sourceforge / jindolf / corelib / LandDef.java
1 /*
2  * land information definition
3  *
4  * License : The MIT License
5  * Copyright(c) 2009 olyutorskii
6  */
7
8 package jp.sourceforge.jindolf.corelib;
9
10 import java.io.IOException;
11 import java.net.URI;
12 import java.nio.charset.Charset;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.Collections;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Locale;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.SortedSet;
22 import java.util.TimeZone;
23 import java.util.TreeSet;
24 import javax.xml.parsers.DocumentBuilder;
25 import org.w3c.dom.Element;
26 import org.xml.sax.SAXException;
27
28 /**
29  * 人狼BBSの各国設定。
30  */
31 public final class LandDef{
32
33     /** 各種イメージの相対なベースURI。 */
34     public static final String IMAGE_RELPATH = "./plugin_wolf/img/";
35
36     /** 顔アイコンURIのテンプレート。 */
37     public static final String DEF_FACE_URI_TMPL =
38         IMAGE_RELPATH + "face{0,number,#00}.jpg";
39     /** デカキャラURIのテンプレート。 */
40     public static final String DEF_BODY_URI_TMPL =
41         IMAGE_RELPATH + "body{0,number,#00}.jpg";
42
43     /** 墓小アイコンのデフォルト相対URI。 */
44     public static final URI DEF_TOMBFACE_URI =
45             URI.create(IMAGE_RELPATH + "face99.jpg").normalize();
46     /** 墓大アイコンのデフォルト相対URI。 */
47     public static final URI DEF_TOMBBODY_URI =
48             URI.create(IMAGE_RELPATH + "body99.jpg").normalize();
49
50     private static final Map<String, LandState> STATE_MAP;
51
52     /** space or tab. */
53     private static final String REG_POSIXBLANK = "\\p{Blank}";
54
55     private static final char HYPHEN_CH = '-';
56     private static final String HYPHEN = "-";
57     private static final String COMMA = ",";
58
59
60     static{
61         STATE_MAP = new HashMap<>();
62         STATE_MAP.put("closed",     LandState.CLOSED);
63         STATE_MAP.put("historical", LandState.HISTORICAL);
64         STATE_MAP.put("active",     LandState.ACTIVE);
65     }
66
67
68     private String landName;
69     private String landId;
70     private String formalName;
71     private String landPrefix;
72     private LandState landState;
73     private int minMembers;
74     private int maxMembers;
75     private URI webURI;
76     private URI cgiURI;
77     private URI tombFaceIconURI;
78     private URI tombBodyIconURI;
79     private String faceURITemplate;
80     private String bodyURITemplate;
81     private Locale locale;
82     private Charset encoding;
83     private TimeZone timeZone;
84     private long startDateTime;
85     private long endDateTime;
86     private String description;
87     private String contactInfo;
88     private int[] invalidVid;
89
90
91     /**
92      * コンストラクタ。
93      */
94     private LandDef(){
95         super();
96         return;
97     }
98
99
100     /**
101      * ハイフンで区切られた整数範囲をパースする。
102      * 「1-3」なら1,2,3を結果に格納する。
103      * @param intSet 格納先Set
104      * @param seq パース対象
105      * @throws IllegalArgumentException 形式が変
106      */
107     private static void parseIntPair(Set<Integer> intSet, CharSequence seq)
108             throws IllegalArgumentException{
109         String token = seq.toString();
110
111         String[] ivalues = token.split(HYPHEN);
112         assert ivalues.length >= 1;
113         if(ivalues.length >= 3){
114             throw new IllegalArgumentException(token);
115         }
116
117         int ivalStart;
118         int ivalEnd;
119         try{
120             ivalStart = Integer.parseInt(ivalues[0]);
121             if(ivalues.length >= 2) ivalEnd = Integer.parseInt(ivalues[1]);
122             else                    ivalEnd = ivalStart;
123         }catch(NumberFormatException e){
124             throw new IllegalArgumentException(token, e);
125         }
126
127         if(ivalStart > ivalEnd){
128             int dummy = ivalStart;
129             ivalStart = ivalEnd;
130             ivalEnd = dummy;
131             assert ivalStart <= ivalEnd;
132         }
133
134         for(int ival = ivalStart; ival <= ivalEnd; ival++){
135             intSet.add(ival);
136         }
137
138         return;
139     }
140
141     /**
142      * コンマとハイフンで区切られた整数の羅列をパースする。
143      * 「10,23-25」なら10,23,24,25を結果に返す。
144      * @param seq パース対象文字列
145      * @return ソートされたIntegerのList
146      * @throws IllegalArgumentException 形式が変。
147      */
148     public static SortedSet<Integer> parseIntList(CharSequence seq)
149             throws IllegalArgumentException{
150         SortedSet<Integer> result = new TreeSet<>();
151
152         if(seq.length() <= 0 ) return result;
153         String str = seq.toString();
154         str = str.replaceAll(REG_POSIXBLANK, "");
155
156         String[] tokens = str.split(COMMA);
157         assert tokens.length >= 1;
158         for(String token : tokens){
159             if(token.length() <= 0) continue;
160             if(token.charAt(0) == HYPHEN_CH || token.endsWith(HYPHEN)){
161                 throw new IllegalArgumentException(token);
162             }
163             parseIntPair(result, token);
164         }
165
166         return result;
167     }
168
169     /**
170      * 国設定のListを返す。
171      * @param builder DOMビルダ
172      * @return List 国設定リスト
173      * @throws IOException IOエラー
174      * @throws SAXException パースエラー
175      */
176     public static List<LandDef> buildLandDefList(DocumentBuilder builder)
177             throws IOException,
178                    SAXException{
179         List<Element> elemList = DomUtils.loadElemList(
180                 builder, XmlResource.I_URL_LANDDEF, "landDef");
181
182         List<LandDef> result = new ArrayList<>(elemList.size());
183
184         for(Element elem : elemList){
185             LandDef landDef = buildLandDef(elem);
186             result.add(landDef);
187         }
188
189         result = Collections.unmodifiableList(result);
190
191         return result;
192     }
193
194     /**
195      * ハイフンをデリミタに持つロケール指定文字列からLocaleを生成する。
196      * @param attrVal ロケール指定文字列
197      * @return Locale
198      */
199     public static Locale buildLocale(CharSequence attrVal){
200         String tag = attrVal.toString();
201         Locale locale = Locale.forLanguageTag(tag);
202         return locale;
203     }
204
205     /**
206      * XML属性を使って国定義の識別子情報を埋める。
207      * @param result 国定義
208      * @param elem 個別のXML国定義要素
209      * @throws SAXException XML属性の記述に関する異常系
210      */
211     private static void fillIdInfo(LandDef result, Element elem)
212             throws SAXException{
213         String landName   = DomUtils.attrRequired(elem, "landName");
214         String landId     = DomUtils.attrRequired(elem, "landId");
215         String formalName = DomUtils.attrRequired(elem, "formalName");
216         String landPrefix = DomUtils.attrRequired(elem, "landPrefix");
217
218         if(    landName  .length() <= 0
219             || landId    .length() <= 0
220             || formalName.length() <= 0 ){
221             throw new SAXException("no identification info");
222         }
223
224         result.landName   = landName;
225         result.landId     = landId;
226         result.formalName = formalName;
227         result.landPrefix = landPrefix;
228
229         return;
230     }
231
232     /**
233      * XML属性を使って国定義の定員情報を埋める。
234      * @param result 国定義
235      * @param elem 個別のXML国定義要素
236      * @throws SAXException XML属性の記述に関する異常系
237      */
238     private static void fillMemberInfo(LandDef result, Element elem)
239             throws SAXException{
240         String minStr = DomUtils.attrRequired(elem, "minMembers");
241         String maxStr = DomUtils.attrRequired(elem, "maxMembers");
242
243         int minMembers = Integer.parseInt(minStr);
244         int maxMembers = Integer.parseInt(maxStr);
245
246         if(    minMembers <= 0
247             || minMembers > maxMembers ){
248             throw new SAXException("invalid member limitation");
249         }
250
251         result.minMembers = minMembers;
252         result.maxMembers = maxMembers;
253
254         return;
255     }
256
257     /**
258      * XML属性を使って国定義のURI情報を埋める。
259      * @param result 国定義
260      * @param elem 個別のXML国定義要素
261      * @throws SAXException XML属性の記述に関する異常系
262      */
263     private static void fillUriInfo(LandDef result, Element elem)
264             throws SAXException{
265         URI webURI = DomUtils.attrToUri(elem, "webURI");
266         URI cgiURI = DomUtils.attrToUri(elem, "cgiURI");
267         if(webURI == null || cgiURI == null){
268             throw new SAXException("no URI");
269         }
270         if(    ! webURI.isAbsolute()
271             || ! cgiURI.isAbsolute() ){
272             throw new SAXException("relative URI");
273         }
274
275         URI tombFaceIconURI = DomUtils.attrToUri(elem, "tombFaceIconURI");
276         URI tombBodyIconURI = DomUtils.attrToUri(elem, "tombBodyIconURI");
277         if(tombFaceIconURI == null) tombFaceIconURI = DEF_TOMBFACE_URI;
278         if(tombBodyIconURI == null) tombBodyIconURI = DEF_TOMBBODY_URI;
279
280         result.webURI          = webURI;
281         result.cgiURI          = cgiURI;
282         result.tombFaceIconURI = tombFaceIconURI;
283         result.tombBodyIconURI = tombBodyIconURI;
284
285         return;
286     }
287
288     /**
289      * XML属性を使って国定義のURIテンプレート情報を埋める。
290      * @param result 国定義
291      * @param elem 個別のXML国定義要素
292      * @throws SAXException XML属性の記述に関する異常系
293      */
294     private static void fillTemplateInfo(LandDef result, Element elem)
295             throws SAXException{
296         String faceURITemplate;
297         String bodyURITemplate;
298
299         faceURITemplate = DomUtils.attrValue(elem, "faceIconURITemplate");
300         bodyURITemplate = DomUtils.attrValue(elem, "bodyIconURITemplate");
301
302         if(faceURITemplate == null) faceURITemplate = DEF_FACE_URI_TMPL;
303         if(bodyURITemplate == null) bodyURITemplate = DEF_BODY_URI_TMPL;
304
305         result.faceURITemplate = faceURITemplate;
306         result.bodyURITemplate = bodyURITemplate;
307
308         return;
309     }
310
311     /**
312      * XML属性を使って国定義の国際化情報を埋める。
313      * @param result 国定義
314      * @param elem 個別のXML国定義要素
315      * @throws SAXException XML属性の記述に関する異常系
316      */
317     private static void fillI18NInfo(LandDef result, Element elem)
318             throws SAXException{
319         String localeText   = DomUtils.attrRequired(elem, "locale");
320         String encodingText = DomUtils.attrRequired(elem, "encoding");
321         String timeZoneText = DomUtils.attrRequired(elem, "timeZone");
322
323         Locale locale = buildLocale(localeText);
324         Charset encoding = Charset.forName(encodingText);
325         TimeZone timeZone = TimeZone.getTimeZone(timeZoneText);
326
327         result.locale   = locale;
328         result.encoding = encoding;
329         result.timeZone = timeZone;
330
331         return;
332     }
333
334     /**
335      * XML属性を使って国定義の日付情報を埋める。
336      * @param result 国定義
337      * @param elem 個別のXML国定義要素
338      * @throws SAXException XML属性の記述に関する異常系
339      */
340     private static void fillDateInfo(LandDef result, Element elem)
341             throws SAXException{
342         long startDateTime;
343         long endDateTime;
344
345         String startDateText = DomUtils.attrRequired(elem, "startDate");
346         String endDateText = elem.getAttribute("endDate");
347
348         startDateTime = DateUtils.parseISO8601(startDateText);
349
350         if(endDateText.length() > 0){
351             endDateTime = DateUtils.parseISO8601(endDateText);
352         }else{
353             endDateTime = -1;
354         }
355
356         if(startDateTime < 0){
357             throw new SAXException("illegal start date " + startDateText);
358         }
359
360         if(endDateTime >= 0 && startDateTime > endDateTime){
361             throw new SAXException("start date is too old " + startDateText);
362         }
363
364         result.startDateTime = startDateTime;
365         result.endDateTime   = endDateTime;
366
367         return;
368     }
369
370     /**
371      * XML属性を使って国定義の各種ステータス情報を埋める。
372      * @param result 国定義
373      * @param elem 個別のXML国定義要素
374      * @throws SAXException XML属性の記述に関する異常系
375      */
376     private static void fillLandInfo(LandDef result, Element elem)
377             throws SAXException{
378         String state = DomUtils.attrRequired(elem, "landState");
379         LandState landState = STATE_MAP.get(state);
380         if(landState == null){
381             throw new SAXException("illegal land status " + state);
382         }
383
384         String description = DomUtils.attrRequired(elem, "description");
385         String contactInfo = DomUtils.attrRequired(elem, "contactInfo");
386
387         String invalidVid = elem.getAttribute("invalidVid");
388         SortedSet<Integer> invalidSet = parseIntList(invalidVid);
389         int[] invalidArray = new int[invalidSet.size()];
390         int pos = 0;
391         for(int vid : invalidSet){
392             invalidArray[pos++] = vid;
393         }
394
395         result.landState   = landState;
396         result.description = description;
397         result.contactInfo = contactInfo;
398         result.invalidVid  = invalidArray;
399
400         return;
401     }
402
403     /**
404      * 個々の国設定をオブジェクトに変換する。
405      * @param elem 国設定要素
406      * @return 国設定オブジェクト
407      * @throws SAXException パースエラー
408      */
409     private static LandDef buildLandDef(Element elem)
410             throws SAXException{
411         LandDef result = new LandDef();
412
413         fillLandInfo    (result, elem);
414         fillIdInfo      (result, elem);
415         fillMemberInfo  (result, elem);
416         fillUriInfo     (result, elem);
417         fillTemplateInfo(result, elem);
418         fillI18NInfo    (result, elem);
419         fillDateInfo    (result, elem);
420
421         return result;
422     }
423
424
425     /**
426      * 国名を得る。
427      * @return 国名
428      */
429     public String getLandName(){
430         return this.landName;
431     }
432
433     /**
434      * 国識別子を得る。
435      * @return 識別子
436      */
437     public String getLandId(){
438         return this.landId;
439     }
440
441     /**
442      * 正式名称を得る。
443      * @return 正式名称
444      */
445     public String getFormalName(){
446         return this.formalName;
447     }
448
449     /**
450      * 各村の前置文字。
451      * F国なら「F」
452      * @return 前置文字
453      */
454     public String getLandPrefix(){
455         return this.landPrefix;
456     }
457
458     /**
459      * 国の状態を得る。
460      * @return 状態
461      */
462     public LandState getLandState(){
463         return this.landState;
464     }
465
466     /**
467      * 最小定員を得る。
468      * @return 最小定員
469      */
470     public int getMinMembers(){
471         return this.minMembers;
472     }
473
474     /**
475      * 最大定員を得る。
476      * @return 最大定員
477      */
478     public int getMaxMembers(){
479         return this.maxMembers;
480     }
481
482     /**
483      * Webアクセス用の入り口URIを得る。
484      * @return 入り口URI
485      */
486     public URI getWebURI(){
487         return this.webURI;
488     }
489
490     /**
491      * クエリーを投げるCGIのURIを得る。
492      * @return CGIのURI
493      */
494     public URI getCgiURI(){
495         return this.cgiURI;
496     }
497
498     /**
499      * 墓画像のURIを得る。
500      * @return 墓URI
501      */
502     public URI getTombFaceIconURI(){
503         return this.tombFaceIconURI;
504     }
505
506     /**
507      * 大きな墓画像のURIを得る。
508      * @return 墓URI
509      */
510     public URI getTombBodyIconURI(){
511         return this.tombBodyIconURI;
512     }
513
514     /**
515      * 顔アイコンURIのテンプレートを得る。
516      * @return Formatter用テンプレート
517      */
518     public String getFaceURITemplate(){
519         return this.faceURITemplate;
520     }
521
522     /**
523      * 全身像アイコンURIのテンプレートを得る。
524      * @return Formatter用テンプレート
525      */
526     public String getBodyURITemplate(){
527         return this.bodyURITemplate;
528     }
529
530     /**
531      * この国のロケールを得る。
532      * @return ロケール
533      */
534     public Locale getLocale(){
535         return this.locale;
536     }
537
538     /**
539      * この国が使うエンコーディングを得る。
540      * @return エンコーディング
541      */
542     public Charset getEncoding(){
543         return this.encoding;
544     }
545
546     /**
547      * この国の時刻表記で使うタイムゾーンのコピーを得る。
548      * @return タイムゾーン
549      */
550     public TimeZone getTimeZone(){
551         Object copy = this.timeZone.clone();
552         TimeZone result = (TimeZone) copy;
553         return result;
554     }
555
556     /**
557      * この国の始まった時刻を得る。
558      * @return 始まった時刻(エポックミリ秒)。
559      */
560     public long getStartDateTime(){
561         return this.startDateTime;
562     }
563
564     /**
565      * この国が発言を打ち切った時刻を得る。
566      * @return 打ち切った時刻(エポックミリ秒)。まだ打ち切っていない場合は負。
567      */
568     public long getEndDateTime(){
569         return this.endDateTime;
570     }
571
572     /**
573      * この国の説明を得る。
574      * @return 説明文字列
575      */
576     public String getDescription(){
577         return this.description;
578     }
579
580     /**
581      * この国の連絡先を得る。
582      * @return 連絡先文字列
583      */
584     public String getContactInfo(){
585         return this.contactInfo;
586     }
587
588     /**
589      * 有効な村IDか否か判定する。
590      * @param vid 村ID
591      * @return 無効な村ならfalse
592      */
593     public boolean isValidVillageId(int vid){
594         int pos = Arrays.binarySearch(this.invalidVid, vid);
595         if(pos >= 0) return false;
596         return true;
597     }
598
599 }