OSDN Git Service

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