4 * License : The MIT License
5 * Copyright(c) 2009 olyutorskii
8 package jp.sourceforge.jovsonz;
10 import java.io.IOException;
13 * JSON STRING型Valueを表す。
25 implements JsValue, CharSequence, Comparable<JsString> {
27 private static final int HEX_BASE = 16;
28 private static final int NIBBLE_WIDE = 4;
29 private static final int NIBBLES_CHAR = Character.SIZE / NIBBLE_WIDE;
31 private static final String ERRMSG_INVESC = "invalid escape character";
32 private static final String ERRMSG_INVCTR = "invalid control character";
34 private final String rawText;
49 * @throws NullPointerException 引数がnull
51 public JsString(CharSequence rawSeq) throws NullPointerException{
53 if(rawSeq == null) throw new NullPointerException();
54 this.rawText = rawSeq.toString();
59 * FFFF形式4桁で16進エスケープされた文字列を読み、
61 * @param source 文字列ソース
63 * @throws IOException 入力エラー
64 * @throws JsParseException 不正表記もしくは意図しない入力終了
66 static char parseHexChar(JsonSource source)
67 throws IOException, JsParseException{
68 char hex1Ch = source.readOrDie();
69 char hex2Ch = source.readOrDie();
70 char hex3Ch = source.readOrDie();
71 char hex4Ch = source.readOrDie();
73 int digit1 = Character.digit(hex1Ch, HEX_BASE);
74 int digit2 = Character.digit(hex2Ch, HEX_BASE);
75 int digit3 = Character.digit(hex3Ch, HEX_BASE);
76 int digit4 = Character.digit(hex4Ch, HEX_BASE);
82 throw new JsParseException(ERRMSG_INVESC, source.getLineNumber());
87 digit <<= NIBBLE_WIDE;
89 digit <<= NIBBLE_WIDE;
91 digit <<= NIBBLE_WIDE;
94 char result = (char) digit;
100 * '\'に続くスペシャルキャラの読み込みを行う。
101 * @param source 文字列ソース
102 * @param app スペシャルキャラ格納文字列
103 * @throws IOException 入出力エラー
104 * @throws JsParseException "\z"などの不正なスペシャルキャラ
107 private static void parseSpecial(JsonSource source, Appendable app)
108 throws IOException, JsParseException{
111 char chData = source.readOrDie();
113 case '"': special = '"'; break;
114 case '\\': special = '\\'; break;
115 case '/': special = '/'; break;
116 case 'b': special = '\b'; break;
117 case 'f': special = '\f'; break;
118 case 'n': special = '\n'; break;
119 case 'r': special = '\r'; break;
120 case 't': special = '\t'; break;
121 case 'u': special = parseHexChar(source); break;
123 throw new JsParseException(ERRMSG_INVESC, source.getLineNumber());
132 * JSON文字列ソースからSTRING型Valueを読み込む。
133 * 別型の可能性のある先頭文字を読み込んだ場合、
134 * ソースに文字を読み戻した後nullが返される。
135 * @param source 文字列ソース
136 * @return STRING型Value。別型の可能性がある場合はnull。
137 * @throws IOException 入力エラー
138 * @throws JsParseException 不正な表記もしくは意図しない入力終了
140 static JsString parseString(JsonSource source)
141 throws IOException, JsParseException{
142 char charHead = source.readOrDie();
144 source.unread(charHead);
148 StringBuilder text = new StringBuilder();
151 char chData = source.readOrDie();
152 if(chData == '"') break;
155 parseSpecial(source, text);
156 }else if(Character.isISOControl(chData)){
157 throw new JsParseException(ERRMSG_INVCTR,
158 source.getLineNumber());
164 JsString result = new JsString(text);
170 * 任意の文字からエスケープ出力用シンボルを得る。
171 * このシンボルは'\'に続けて用いられる1文字である。
174 * @return エスケープ出力用シンボル。
175 * 1文字エスケープの必要がない場合は'\0'
177 private static char escapeSymbol(char ch){
180 case '"' : result = '"'; break;
181 case '\\': result = '\\'; break;
182 case '/' : result = '/'; break;
183 case '\b': result = 'b'; break;
184 case '\f': result = 'f'; break;
185 case '\n': result = 'n'; break;
186 case '\r': result = 'r'; break;
187 case '\t': result = 't'; break;
188 default: result = '\0'; break;
198 * @return 特殊文字出力がエスケープされた時にtrue
199 * @throws IOException 出力エラー
201 private static boolean dumpSpecialChar(Appendable appout, char ch)
203 char esc1ch = escapeSymbol(ch);
206 appout.append('\\').append(esc1ch);
207 }else if(Character.isISOControl(ch)){
209 String hex = "0000" + Integer.toHexString(ch);
210 hex = hex.substring(hex.length() - NIBBLES_CHAR);
211 appout.append("\\u").append(hex);
220 * JSON STRING型Value形式で文字列を出力する。
223 * @throws IOException 出力エラー
225 public static void dumpString(Appendable appout, CharSequence seq)
229 int length = seq.length();
230 for(int pos = 0; pos < length; pos++){
231 char ch = seq.charAt(pos);
232 if( ! dumpSpecialChar(appout, ch) ){
243 * JSON STRING型Value形式の文字列を返す。
245 * @return STRING型表記に変換された文字列
248 public static StringBuilder escapeText(CharSequence seq){
249 StringBuilder result = new StringBuilder();
251 dumpString(result, seq);
252 }catch(IOException e){
254 throw new AssertionError(e);
261 * 常に{@link JsTypes#STRING}を返す。
262 * @return {@inheritDoc}
265 public JsTypes getJsTypes(){
266 return JsTypes.STRING;
271 * この実装ではthisの出現のみを通知する。
272 * @param visitor {@inheritDoc}
273 * @throws JsVisitException {@inheritDoc}
276 public void traverse(ValueVisitor visitor)
277 throws JsVisitException{
278 visitor.visitValue(this);
285 * @return {@inheritDoc}
288 public int hashCode(){
289 return this.rawText.hashCode();
295 * {@link java.lang.String#equals(Object)}に準ずる。
296 * @param obj {@inheritDoc}
297 * @return {@inheritDoc}
300 public boolean equals(Object obj){
301 if(this == obj) return true;
303 if( ! (obj instanceof JsString) ) return false;
304 JsString string = (JsString) obj;
306 return this.rawText.equals(string.rawText);
311 * STRING型Valueを昇順に順序付ける。
312 * {@link java.lang.String#compareTo(String)}に準ずる。
313 * @param value {@inheritDoc}
314 * @return {@inheritDoc}
317 public int compareTo(JsString value){
318 if(this == value) return 0;
319 if(value == null) return +1;
320 return this.rawText.compareTo(value.rawText);
326 * @param index {@inheritDoc}
327 * @return {@inheritDoc}
328 * @throws IndexOutOfBoundsException {@inheritDoc}
331 public char charAt(int index)
332 throws IndexOutOfBoundsException{
333 return this.rawText.charAt(index);
339 * @return {@inheritDoc}
343 return this.rawText.length();
349 * @param start {@inheritDoc}
350 * @param end {@inheritDoc}
351 * @return {@inheritDoc}
352 * @throws IndexOutOfBoundsException {@inheritDoc}
355 public CharSequence subSequence(int start, int end)
356 throws IndexOutOfBoundsException{
357 return this.rawText.subSequence(start, end);
361 * クォーテーションやエスケープ処理の施されていない生の文字列を返す。
364 public String toRawString(){
370 * クォーテーションとエスケープ処理の施された文字列表記を生成する。
371 * JSON表記の一部としての利用も可能。
372 * @return {@inheritDoc}
375 public String toString(){
376 StringBuilder string = escapeText(this.rawText);
377 return string.toString();