4 * License : The MIT License
5 * Copyright(c) 2009 olyutorskii
8 package jp.sfjp.jindolf.dxchg;
10 import java.awt.Color;
11 import java.io.FileNotFoundException;
12 import java.io.IOException;
13 import java.nio.ByteBuffer;
14 import java.nio.CharBuffer;
15 import java.nio.charset.CharacterCodingException;
16 import java.nio.charset.Charset;
17 import java.nio.charset.CharsetEncoder;
18 import java.util.Collections;
19 import java.util.LinkedList;
20 import java.util.List;
21 import java.util.Locale;
22 import java.util.Properties;
24 import java.util.SortedSet;
25 import java.util.TreeSet;
26 import java.util.logging.Logger;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29 import jp.sfjp.jindolf.ResourceManager;
30 import jp.sfjp.jindolf.data.Avatar;
31 import jp.sfjp.jindolf.data.Village;
32 import jp.sourceforge.jindolf.corelib.Destiny;
33 import jp.sourceforge.jindolf.corelib.GameRole;
36 * まちゅ氏運営のまとめサイト(wolfbbs)に関する諸々。
40 * @see <a href="https://wolfbbs.jp/">まとめサイト</a>
41 * @see <a href="https://pukiwiki.osdn.jp/">PukiWiki</a>
43 public final class WolfBBS{
46 public static final String COMMENTLINE;
48 private static final String WIKICHAR = "#&[]()<>+-*:|~/,'%?";
49 private static final Pattern WIKINAME_PATTERN =
50 Pattern.compile("[A-Z][a-z]+([A-Z])[a-z]+");
52 private static final String FACEICONSET =
53 "resources/wolfbbs/faceIconSet.properties";
54 private static final String ORDER_PREFIX = "iconset.order.";
55 private static final List<FaceIconSet> FACEICONSET_LIST =
58 private static final Charset CHARSET_EUC = Charset.forName("EUC-JP");
60 private static final String WOLFBBS_URL = "http://wolfbbs.jp/";
62 private static final Color COLOR_INNOCENT = new Color(0xb7bad3);
63 private static final Color COLOR_WOLF = new Color(0xe0b8b8);
64 private static final Color COLOR_HAMSTER = new Color(0xb9d0be);
65 private static final Color COLOR_DEAD = new Color(0xaaaaaa);
66 private static final Color COLOR_ALIVE = new Color(0xffffff);
68 private static final Logger LOGGER = Logger.getAnonymousLogger();
73 }catch(FileNotFoundException e){
74 throw new ExceptionInInitializerError(e);
77 StringBuilder wikicomment = new StringBuilder();
78 wikicomment.append("// ");
79 while(wikicomment.length() < 72){
80 wikicomment.append('=');
82 wikicomment.append('\n');
83 COMMENTLINE = wikicomment.toString();
92 throw new AssertionError();
99 * @throws FileNotFoundException リソースが不明
101 private static void loadFaceIconSet() throws FileNotFoundException {
102 Properties properties = ResourceManager.getProperties(FACEICONSET);
103 if(properties == null){
104 LOGGER.severe("顔アイコンセットの読み込みに失敗しました");
105 throw new FileNotFoundException();
108 loadFaceIconSet(properties);
116 * @param properties プロパティ
117 * @throws FileNotFoundException リソースが不明
119 private static void loadFaceIconSet(Properties properties)
120 throws FileNotFoundException {
121 String codeCheck = properties.getProperty("codeCheck");
122 if( codeCheck == null
123 || codeCheck.length() != 1
124 || codeCheck.charAt(0) != '\u72fc'){ // 「狼」
128 +"native2ascii は正しく適用しましたか?");
129 throw new FileNotFoundException();
132 Set<Object> keySet = properties.keySet();
134 SortedSet<Integer> orderSet = new TreeSet<>();
135 for(Object keyObj : keySet){
136 if(keyObj == null) continue;
137 String key = keyObj.toString();
138 if( ! key.startsWith(ORDER_PREFIX) ) continue;
139 key = key.replace(ORDER_PREFIX, "");
142 order = Integer.valueOf(key);
143 }catch(NumberFormatException e){
149 for(Integer orderNum : orderSet){
150 String setName = properties.getProperty(ORDER_PREFIX + orderNum);
151 FaceIconSet iconSet = loadFaceIconSet(properties, setName);
152 FACEICONSET_LIST.add(iconSet);
161 * @param properties プロパティ
162 * @param setName アイコンセット名
165 private static FaceIconSet loadFaceIconSet(Properties properties,
167 String author = properties.getProperty(setName + ".author");
168 String caption = properties.getProperty(setName + ".caption");
169 String urlText = properties.getProperty(setName + ".url");
171 FaceIconSet iconSet = new FaceIconSet(caption, author, urlText);
173 List<Avatar> avatarList = Avatar.getPredefinedAvatarList();
174 for(Avatar avatar : avatarList){
175 String identifier = avatar.getIdentifier();
176 String key = setName + ".iconWiki." + identifier;
177 String wiki = properties.getProperty(key);
178 iconSet.registIconWiki(avatar, wiki);
187 * @return 顔アイコンセットのリスト
189 public static List<FaceIconSet> getFaceIconSetList(){
190 List<FaceIconSet> result =
191 Collections.unmodifiableList(FACEICONSET_LIST);
196 * 任意の文字がWikiの特殊キャラクタか否か判定する。
199 * @return 特殊キャラクタならtrue
201 public static boolean isWikiChar(char ch){
202 if(WIKICHAR.indexOf(ch) < 0) return false;
207 * Wiki特殊文字を数値参照文字でエスケープする。
209 * @param seq Wiki特殊文字を含むかもしれない文字列。
210 * @return エスケープされた文字列
212 public static CharSequence escapeWikiChar(CharSequence seq){
213 StringBuilder result = new StringBuilder();
215 int seqLength = seq.length();
216 for(int pos = 0; pos < seqLength; pos++){
217 char ch = seq.charAt(pos);
220 appendNumCharRef(result, ch);
221 }catch(IOException e){
234 * WikiNameを数値参照文字でエスケープする。
236 * @param seq WikiNameを含むかもしれない文字列
237 * @return エスケープされた文字列。
238 * @see <a href="https://pukiwiki.osdn.jp/?WikiName">WikiName</a>
240 public static CharSequence escapeWikiName(CharSequence seq){
241 StringBuilder result = null;
242 Matcher matcher = WIKINAME_PATTERN.matcher(seq);
245 while(matcher.find(pos)){
246 int matchStart = matcher.start();
247 int matchEnd = matcher.end();
248 int capStart = matcher.start(1);
249 int capEnd = matcher.end(1);
251 if(result == null) result = new StringBuilder();
252 result.append(seq, pos, matchStart);
253 result.append(seq, matchStart, capStart);
255 appendNumCharRef(result, seq.charAt(capStart));
256 }catch(IOException e){
260 result.append(seq, capEnd, matchEnd);
265 if(pos == 0 || result == null) return seq;
267 result.append(seq, pos, seq.length());
273 * 任意の文字列をWiki表記へ変換する。
278 public static CharSequence escapeWikiSyntax(CharSequence seq){
279 CharSequence result = seq;
280 result = escapeWikiChar(result); // この順番は大事
281 result = escapeWikiName(result);
282 // TODO さらにURLとメールアドレスのエスケープも
287 * ブラケットに入れる文字をエスケープする。
290 * @return エスケープされた文字列
292 public static CharSequence escapeWikiBracket(CharSequence seq){
293 StringBuilder result = new StringBuilder();
295 int seqLength = seq.length();
296 for(int pos = 0; pos < seqLength; pos++){
297 char ch = seq.charAt(pos);
300 case '#': ch = '#'; break;
301 case '&': ch = '&'; break;
302 case '[': ch = '['; break;
303 case ']': ch = ']'; break;
304 case '<': ch = '<'; break;
305 case '>': ch = '>'; break;
314 while(result.length() > 0 && result.charAt(0) == '/'){
315 result.deleteCharAt(0);
318 resultLength = result.length();
319 for(int pos = resultLength - 1; pos >= 0; pos--){
320 char ch = result.charAt(pos);
322 result.deleteCharAt(pos);
325 resultLength = result.length();
326 for(int pos = 1; pos < resultLength - 1; pos++){
327 char ch = result.charAt(pos);
329 result.setCharAt(pos, ':');
333 resultLength = result.length();
334 if(resultLength == 1 && result.charAt(0) == ':'){
335 result.setCharAt(0, ':');
342 * 数値参照文字に変換された文字を追加する。
344 * 例){@literal 'D' => "D}"
349 * @throws java.io.IOException 入出力エラー。文字列の場合はありえない。
351 public static Appendable appendNumCharRef(Appendable app, char ch)
356 String hex = Integer.toHexString(ival);
365 * 任意の文字を数値参照文字列に変換する。
367 * 例){@literal 'D' => "D"}
372 public static CharSequence toNumCharRef(char ch){
373 StringBuilder result = new StringBuilder(8);
375 appendNumCharRef(result, ch);
376 }catch(IOException e){
384 * ColorのRGB各成分をWikiカラー表記に変換する。
391 public static String cnvWikiColor(Color color){
392 int packRGB = color.getRGB();
394 String txtRGB = Integer.toHexString(packRGB);
395 String leadRGB = "00000000" + txtRGB;
396 int chopLen = leadRGB.length() - 6;
397 String fixed = leadRGB.substring(chopLen);
398 String result = "#" + fixed;
409 public static Color evenColor(Color color){
410 int red = color.getRed();
411 int green = color.getGreen();
412 int blue = color.getBlue();
414 float[] hsb = Color.RGBtoHSB(red, green, blue, null);
425 Color result = Color.getHSBColor(h, s, b);
436 public static Color getTeamColor(GameRole role){
445 result = COLOR_INNOCENT;
452 result = COLOR_HAMSTER;
468 public static String getRoleIconWiki(GameRole role){
473 result = "&char(村人,nolink);";
476 result = "&char(人狼,nolink);";
479 result = "&char(占い師,nolink);";
482 result = "&char(霊能者,nolink);";
485 result = "&char(狂人,nolink);";
488 result = "&char(狩人,nolink);";
491 result = "&char(共有者,nolink);";
494 result = "&char(ハムスター人間,nolink);";
511 public static Color getDestinyColor(Destiny destiny){
513 if(destiny == Destiny.ALIVE) result = COLOR_ALIVE;
514 else result = COLOR_DEAD;
519 * そのまままとめサイトパス名に使えそうなシンプルな文字か判定する。
522 * @return まとめサイトパス名に使えそうならtrue
524 private static boolean isSimpleIdToken(char ch){
525 if('0' <= ch && ch <= '9') return true;
526 if('A' <= ch && ch <= 'Z') return true;
527 if('a' <= ch && ch <= 'z') return true;
528 if(ch == '-' || ch == '_') return true;
533 * プレイヤーIDを構成する文字からパス名を組み立てる。
539 private static StringBuilder encodeId(StringBuilder seq, char ch){
540 if(isSimpleIdToken(ch)){
545 CharBuffer cbuf = CharBuffer.allocate(1);
549 CharsetEncoder encoder = CHARSET_EUC.newEncoder();
552 bytebuf = encoder.encode(cbuf);
553 }catch(CharacterCodingException e){
558 int limit = bytebuf.limit();
559 while(bytebuf.position() < limit){
560 int iVal = bytebuf.get();
561 if(iVal < 0) iVal += 0x0100;
562 String hex = Integer.toHexString(iVal).toUpperCase(Locale.JAPAN);
564 if(hex.length() < 2) seq.append('0');
572 * プレイヤーIDからパス名の一部を予測する。
575 * @return .htmlを抜いたパス名
577 private static StringBuilder encodeId(CharSequence id){
578 StringBuilder result = new StringBuilder();
579 int length = id.length();
580 for(int pt = 0; pt < length; pt++){
581 char ch = id.charAt(pt);
582 encodeId(result, ch);
588 * プレイヤーIDからまとめサイト上の個人ページを推測する。
591 * @return 個人ページURL文字列
593 public static String encodeURLFromId(CharSequence id){
594 CharSequence encodedId = encodeId(id);
596 String result = WOLFBBS_URL + encodedId + ".html";
602 * キャスト紹介ジェネレータ出力のURLを得る。
605 * @return ジェネレータ出力URL
607 public static String getCastGeneratorUrl(Village village){
608 String villageName = village.getVillageName();
610 StringBuilder txt = new StringBuilder();
611 txt.append(WOLFBBS_URL);
612 txt.append(villageName);
613 txt.append("%C2%BC.html");
615 String result = txt.toString();