2 * land information definition
\r
4 * License : The MIT License
\r
5 * Copyright(c) 2009 olyutorskii
\r
8 package jp.sourceforge.jindolf.corelib;
\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
31 public final class LandDef{
\r
33 /** 各種イメージの相対なベースURI。 */
\r
34 public static final String IMAGE_RELPATH = "./plugin_wolf/img/";
\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
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
50 private static final Map<String, LandState> STATE_MAP;
\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
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
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
94 * ハイフンで区切られた整数範囲をパースする。
\r
95 * 「1-3」なら1,2,3を結果に格納する。
\r
96 * @param intSet 格納先Set
\r
98 * @throws IllegalArgumentException 形式が変
\r
100 private static void parseIntPair(Set<Integer> intSet, CharSequence seq)
\r
101 throws IllegalArgumentException{
\r
102 String token = seq.toString();
\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
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
120 if(ivalStart > ivalEnd){
\r
121 int dummy = ivalStart;
\r
122 ivalStart = ivalEnd;
\r
124 assert ivalStart <= ivalEnd;
\r
127 for(int ival = ivalStart; ival <= ivalEnd; ival++){
\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
141 public static SortedSet<Integer> parseIntList(CharSequence seq)
\r
142 throws IllegalArgumentException{
\r
143 SortedSet<Integer> result = new TreeSet<Integer>();
\r
145 if(seq.length() <= 0 ) return result;
\r
146 String str = seq.toString();
\r
147 str = str.replaceAll("\\p{Blank}", "");
\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
157 parseIntPair(result, token);
\r
165 * @param builder DOMビルダ
\r
166 * @return List 国設定リスト
\r
167 * @throws IOException IOエラー
\r
168 * @throws SAXException パースエラー
\r
170 public static List<LandDef> buildLandDefList(DocumentBuilder builder)
\r
171 throws IOException,
\r
173 List<Element> elemList = DomUtils.loadElemList(
\r
174 builder, XmlResource.I_URL_LANDDEF, "landDef");
\r
176 List<LandDef> result = new ArrayList<LandDef>(elemList.size());
\r
178 for(Element elem : elemList){
\r
179 LandDef landDef = buildLandDef(elem);
\r
180 result.add(landDef);
\r
183 result = Collections.unmodifiableList(result);
\r
189 * ハイフンをデリミタに持つロケール指定文字列からLocaleを生成する。
\r
190 * @param attrVal ロケール指定文字列
\r
193 public static Locale buildLocale(CharSequence attrVal){
\r
195 String country = "";
\r
196 String variant = "";
\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
203 Locale locale = new Locale(lang, country, variant);
\r
209 * XML属性を使って国定義の識別子情報を埋める。
\r
210 * @param result 国定義
\r
211 * @param elem 個別のXML国定義要素
\r
212 * @throws SAXException XML属性の記述に関する異常系
\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
221 if( landName .length() <= 0
\r
222 || landId .length() <= 0
\r
223 || formalName.length() <= 0 ){
\r
224 throw new SAXException("no identification info");
\r
227 result.landName = landName;
\r
228 result.landId = landId;
\r
229 result.formalName = formalName;
\r
230 result.landPrefix = landPrefix;
\r
236 * XML属性を使って国定義の定員情報を埋める。
\r
237 * @param result 国定義
\r
238 * @param elem 個別のXML国定義要素
\r
239 * @throws SAXException XML属性の記述に関する異常系
\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
246 int minMembers = Integer.parseInt(minStr);
\r
247 int maxMembers = Integer.parseInt(maxStr);
\r
249 if( minMembers <= 0
\r
250 || minMembers > maxMembers ){
\r
251 throw new SAXException("invalid member limitation");
\r
254 result.minMembers = minMembers;
\r
255 result.maxMembers = maxMembers;
\r
261 * XML属性を使って国定義のURI情報を埋める。
\r
262 * @param result 国定義
\r
263 * @param elem 個別のXML国定義要素
\r
264 * @throws SAXException XML属性の記述に関する異常系
\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
273 if( ! webURI.isAbsolute()
\r
274 || ! cgiURI.isAbsolute() ){
\r
275 throw new SAXException("relative URI");
\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
283 result.webURI = webURI;
\r
284 result.cgiURI = cgiURI;
\r
285 result.tombFaceIconURI = tombFaceIconURI;
\r
286 result.tombBodyIconURI = tombBodyIconURI;
\r
292 * XML属性を使って国定義のURIテンプレート情報を埋める。
\r
293 * @param result 国定義
\r
294 * @param elem 個別のXML国定義要素
\r
295 * @throws SAXException XML属性の記述に関する異常系
\r
297 private static void fillTemplateInfo(LandDef result, Element elem)
\r
298 throws SAXException{
\r
299 String faceURITemplate;
\r
300 String bodyURITemplate;
\r
302 faceURITemplate = DomUtils.attrValue(elem, "faceIconURITemplate");
\r
303 bodyURITemplate = DomUtils.attrValue(elem, "bodyIconURITemplate");
\r
305 if(faceURITemplate == null) faceURITemplate = DEF_FACE_URI_TMPL;
\r
306 if(bodyURITemplate == null) bodyURITemplate = DEF_BODY_URI_TMPL;
\r
308 result.faceURITemplate = faceURITemplate;
\r
309 result.bodyURITemplate = bodyURITemplate;
\r
315 * XML属性を使って国定義の国際化情報を埋める。
\r
316 * @param result 国定義
\r
317 * @param elem 個別のXML国定義要素
\r
318 * @throws SAXException XML属性の記述に関する異常系
\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
326 Locale locale = buildLocale(localeText);
\r
327 Charset encoding = Charset.forName(encodingText);
\r
328 TimeZone timeZone = TimeZone.getTimeZone(timeZoneText);
\r
330 result.locale = locale;
\r
331 result.encoding = encoding;
\r
332 result.timeZone = timeZone;
\r
338 * XML属性を使って国定義の日付情報を埋める。
\r
339 * @param result 国定義
\r
340 * @param elem 個別のXML国定義要素
\r
341 * @throws SAXException XML属性の記述に関する異常系
\r
343 private static void fillDateInfo(LandDef result, Element elem)
\r
344 throws SAXException{
\r
345 long startDateTime;
\r
348 String startDateText = DomUtils.attrRequired(elem, "startDate");
\r
349 String endDateText = elem.getAttribute("endDate");
\r
351 startDateTime = DateUtils.parseISO8601(startDateText);
\r
353 if(endDateText.length() > 0){
\r
354 endDateTime = DateUtils.parseISO8601(endDateText);
\r
359 if(startDateTime < 0){
\r
360 throw new SAXException("illegal start date " + startDateText);
\r
363 if(endDateTime >= 0 && startDateTime > endDateTime){
\r
364 throw new SAXException("start date is too old " + startDateText);
\r
367 result.startDateTime = startDateTime;
\r
368 result.endDateTime = endDateTime;
\r
374 * XML属性を使って国定義の各種ステータス情報を埋める。
\r
375 * @param result 国定義
\r
376 * @param elem 個別のXML国定義要素
\r
377 * @throws SAXException XML属性の記述に関する異常系
\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
387 String description = DomUtils.attrRequired(elem, "description");
\r
388 String contactInfo = DomUtils.attrRequired(elem, "contactInfo");
\r
390 String invalidVid = elem.getAttribute("invalidVid");
\r
391 SortedSet<Integer> invalidSet = parseIntList(invalidVid);
\r
392 int[] invalidArray = new int[invalidSet.size()];
\r
394 for(int vid : invalidSet){
\r
395 invalidArray[pos++] = vid;
\r
398 result.landState = landState;
\r
399 result.description = description;
\r
400 result.contactInfo = contactInfo;
\r
401 result.invalidVid = invalidArray;
\r
407 * 個々の国設定をオブジェクトに変換する。
\r
408 * @param elem 国設定要素
\r
409 * @return 国設定オブジェクト
\r
410 * @throws SAXException パースエラー
\r
412 private static LandDef buildLandDef(Element elem)
\r
413 throws SAXException{
\r
414 LandDef result = new LandDef();
\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
432 public String getLandName(){
\r
433 return this.landName;
\r
440 public String getLandId(){
\r
441 return this.landId;
\r
448 public String getFormalName(){
\r
449 return this.formalName;
\r
457 public String getLandPrefix(){
\r
458 return this.landPrefix;
\r
465 public LandState getLandState(){
\r
466 return this.landState;
\r
473 public int getMinMembers(){
\r
474 return this.minMembers;
\r
481 public int getMaxMembers(){
\r
482 return this.maxMembers;
\r
486 * Webアクセス用の入り口URIを得る。
\r
489 public URI getWebURI(){
\r
490 return this.webURI;
\r
494 * クエリーを投げるCGIのURIを得る。
\r
497 public URI getCgiURI(){
\r
498 return this.cgiURI;
\r
505 public URI getTombFaceIconURI(){
\r
506 return this.tombFaceIconURI;
\r
513 public URI getTombBodyIconURI(){
\r
514 return this.tombBodyIconURI;
\r
518 * 顔アイコンURIのテンプレートを得る。
\r
519 * @return Formatter用テンプレート
\r
521 public String getFaceURITemplate(){
\r
522 return this.faceURITemplate;
\r
526 * 全身像アイコンURIのテンプレートを得る。
\r
527 * @return Formatter用テンプレート
\r
529 public String getBodyURITemplate(){
\r
530 return this.bodyURITemplate;
\r
537 public Locale getLocale(){
\r
538 return this.locale;
\r
542 * この国が使うエンコーディングを得る。
\r
545 public Charset getEncoding(){
\r
546 return this.encoding;
\r
550 * この国の時刻表記で使うタイムゾーンのコピーを得る。
\r
553 public TimeZone getTimeZone(){
\r
554 TimeZone result = (TimeZone)( this.timeZone.clone() );
\r
560 * @return 始まった時刻(エポックミリ秒)。
\r
562 public long getStartDateTime(){
\r
563 return this.startDateTime;
\r
567 * この国が発言を打ち切った時刻を得る。
\r
568 * @return 打ち切った時刻(エポックミリ秒)。まだ打ち切っていない場合は負。
\r
570 public long getEndDateTime(){
\r
571 return this.endDateTime;
\r
578 public String getDescription(){
\r
579 return this.description;
\r
586 public String getContactInfo(){
\r
587 return this.contactInfo;
\r
593 * @return 無効な村ならfalse
\r
595 public boolean isValidVillageId(int vid){
\r
596 int pos = Arrays.binarySearch(this.invalidVid, vid);
\r
597 if(pos >= 0) return false;
\r