OSDN Git Service

初回インポート
[jovsonz/Jovsonz.git] / src / main / java / jp / sourceforge / jovsonz / JsObject.java
1 /*
2  * JSON object value
3  *
4  * License : The MIT License
5  * Copyright(c) 2009 olyutorskii
6  */
7
8 package jp.sourceforge.jovsonz;
9
10 import java.io.IOException;
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Set;
17 import java.util.TreeMap;
18
19 /**
20  * JSON OBJECT型Valueを表す。
21  * PAIR名と子要素の組(PAIR)の集合を反映する。
22  * PAIR名の並び順に関しては未定義とする。
23  * <h1>表記例</h1>
24  * <pre>
25  * {
26  *     "Name" : "Joe" ,
27  *     "Age" : 19
28  * }
29  * </pre>
30  */
31 public class JsObject
32         implements JsComposition<JsPair> {
33
34     private static final String ERRMSG_NOOBJECTCOMMA =
35             "missing comma in OBJECT";
36     private static final String ERRMSG_NOHASHNAME =
37             "no hash name in OBJECT";
38     private static final String ERRMSG_NOHASHSEP =
39             "missing hash separator(:) in OBJECT";
40     private static final String ERRMSG_NOHASHVAL =
41             "no hash value in OBJECT";
42
43     private final Map<String, JsPair> pairMap =
44             new TreeMap<String, JsPair>();
45     private final Collection<JsPair> pairCollection = this.pairMap.values();
46
47     private boolean changed = false;
48
49
50     /**
51      * コンストラクタ。
52      */
53     public JsObject(){
54         super();
55         return;
56     }
57
58     /**
59      * JSON文字列ソースからOBJECT型Valueを読み込む。
60      * さらに子Valueへとパース解析が進む可能性がある。
61      * 別型の可能性のある先頭文字を読み込んだ場合、
62      * ソースに文字を読み戻した後nullが返される。
63      * @param source 文字列ソース
64      * @return OBJECT型Value。別型の可能性がある場合はnull。
65      * @throws IOException 入力エラー
66      * @throws JsParseException 不正な表記もしくは意図しない入力終了
67      */
68     static JsObject parseObject(JsonSource source)
69             throws IOException, JsParseException{
70         char charHead = source.readOrDie();
71         if(charHead != '{'){
72             source.unread(charHead);
73             return null;
74         }
75
76         JsObject result = new JsObject();
77
78         for(;;){
79             source.skipWhiteSpace();
80             char chData = source.readOrDie();
81             if(chData == '}') break;
82
83             if(result.isEmpty()){
84                 source.unread(chData);
85             }else{
86                 if(chData != ','){
87                     throw new JsParseException(ERRMSG_NOOBJECTCOMMA,
88                                                source.getLineNumber() );
89                 }
90                 source.skipWhiteSpace();
91             }
92
93             JsString name = JsString.parseString(source);
94             if(name == null){
95                 throw new JsParseException(ERRMSG_NOHASHNAME,
96                                            source.getLineNumber() );
97             }
98
99             source.skipWhiteSpace();
100             chData = source.readOrDie();
101             if(chData != ':'){
102                 throw new JsParseException(ERRMSG_NOHASHSEP,
103                                            source.getLineNumber() );
104             }
105
106             JsValue value = Json.parseValue(source);
107             if(value == null){
108                 throw new JsParseException(ERRMSG_NOHASHVAL,
109                                            source.getLineNumber() );
110             }
111
112             result.putValue(name.toRawString(), value);
113         }
114
115         return result;
116     }
117
118     /**
119      * {@inheritDoc}
120      * 常に{@link JsTypes#OBJECT}を返す。
121      * @return {@inheritDoc}
122      */
123     @Override
124     public JsTypes getJsTypes(){
125         return JsTypes.OBJECT;
126     }
127
128     /**
129      * このValueおよび子孫に変更があったか判定する。
130      * PAIRの追加・削除が行われたか、
131      * もしくはPAIRのValue値いずれかに変更が認められれば、
132      * このOBJECT型Valueに変更があったとみなされる。
133      * @return {@inheritDoc}
134      */
135     @Override
136     public boolean hasChanged(){
137         if(this.changed) return true;
138
139         for(JsPair pair : this){
140             JsValue value = pair.getValue();
141             if( ! (value instanceof JsComposition) ) continue;
142             JsComposition composition = (JsComposition) value;
143             if(composition.hasChanged()) return true;
144         }
145
146         return false;
147     }
148
149     /**
150      * このValueおよび子孫に変更がなかったことにする。
151      */
152     @Override
153     public void setUnchanged(){
154         this.changed = false;
155
156         for(JsPair pair : this){
157             JsValue value = pair.getValue();
158             if( ! (value instanceof JsComposition) ) continue;
159             JsComposition composition = (JsComposition) value;
160             composition.setUnchanged();
161         }
162
163         return;
164     }
165
166     /**
167      * 深さ優先探索を行い各種構造の出現をビジターに通知する。
168      * thisを通知した後、PAIRの各名前およびValueを順に訪問し、
169      * 最後に閉じ括弧を通知する。
170      * PAIRの訪問順に関しては未定義。
171      * @param visitor {@inheritDoc}
172      * @throws JsVisitException {@inheritDoc}
173      */
174     @Override
175     public void traverse(ValueVisitor visitor) throws JsVisitException{
176         visitor.visitValue(this);
177
178         for(JsPair pair : this){
179             String name   = pair.getName();
180             JsValue value = pair.getValue();
181             visitor.visitPairName(name);
182             value.traverse(visitor);
183         }
184
185         visitor.visitCompositionClose(this);
186
187         return;
188     }
189
190     /**
191      * PAIR総数を返す。
192      * @return PAIR総数
193      */
194     @Override
195     public int size(){
196         return this.pairMap.size();
197     }
198
199     /**
200      * PAIR集合が空か判定する。
201      * @return 空ならtrue
202      */
203     @Override
204     public boolean isEmpty(){
205         return this.pairMap.isEmpty();
206     }
207
208     /**
209      * PAIR集合を空にする。
210      */
211     @Override
212     public void clear(){
213         if(this.pairMap.size() > 0) this.changed = true;
214         this.pairMap.clear();
215         return;
216     }
217
218     /**
219      * ハッシュ値を返す。
220      * 全てのPAIRのハッシュ値からその都度合成される。高コスト注意!。
221      * @return {@inheritDoc}
222      */
223     @Override
224     public int hashCode(){
225         return this.pairMap.hashCode();
226     }
227
228     /**
229      * 等価判定を行う。
230      * 双方のPAIR数が一致し、
231      * 全てのPAIR名およびそれに対応付けられたValueが一致した場合のみ
232      * 等価と判断される。
233      * @param obj {@inheritDoc}
234      * @return {@inheritDoc}
235      */
236     @Override
237     public boolean equals(Object obj){
238         if(this == obj) return true;
239
240         if( ! (obj instanceof JsObject) ) return false;
241         JsObject composit = (JsObject) obj;
242
243         return this.pairMap.equals(composit.pairMap);
244     }
245
246     /**
247      * 名前とValueからPAIRを登録する。
248      * @param name 名前
249      * @param value Value
250      * @return 旧Value。同じ内容のPAIRがすでに存在していたらnull
251      * @throws NullPointerException 引数のいずれかがnull
252      */
253     public JsValue putValue(String name, JsValue value)
254             throws NullPointerException{
255         if(name  == null) throw new NullPointerException();
256         if(value == null) throw new NullPointerException();
257
258         JsValue oldValue = null;
259         JsPair oldPair = this.pairMap.get(name);
260         if(oldPair != null){
261             oldValue = oldPair.getValue();
262             if(value.equals(oldValue)) return null;
263         }
264
265         JsPair newPair = new JsPair(name, value);
266         this.pairMap.put(name, newPair);
267
268         this.changed = true;
269         return oldValue;
270     }
271
272     /**
273      * PAIR名からValueを取得する。
274      * @param name PAIR名
275      * @return 対応するValue。見つからなければnull
276      */
277     public JsValue getValue(String name){
278         JsPair pair = this.pairMap.get(name);
279         if(pair == null) return null;
280         JsValue value = pair.getValue();
281         return value;
282     }
283
284     /**
285      * PAIRを追加する。
286      * 同じPAIR名を持つPAIRは無条件に上書きされる。
287      * @param pair PAIR
288      */
289     public void putPair(JsPair pair){
290         this.pairMap.put(pair.getName(), pair);
291         return;
292     }
293
294     /**
295      * PAIR名からPAIRを返す。
296      * @param name PAIR名
297      * @return PAIR。見つからなければnull
298      */
299     public JsPair getPair(String name){
300         JsValue value = getValue(name);
301         if(value == null) return null;
302
303         return new JsPair(name, value);
304     }
305
306     /**
307      * 指定した名前のPAIRを削除する。
308      * @param name PAIR名
309      * @return 消されたPAIR。該当するPAIRがなければnull
310      */
311     public JsPair remove(String name){
312         JsPair oldPair = this.pairMap.remove(name);
313         if(oldPair != null) this.changed = true;
314
315         return oldPair;
316     }
317
318     /**
319      * 保持する全PAIRのPAIR名の集合を返す。
320      * @return すべての名前
321      */
322     public Set<String> nameSet(){
323         return this.pairMap.keySet();
324     }
325
326     /**
327      * PAIRのリストを返す。
328      * このリストを上書き操作しても影響はない。
329      * @return PAIRリスト
330      */
331     public List<JsPair> getPairList(){
332         List<JsPair> result = new ArrayList<JsPair>(this.pairMap.size());
333
334         for(JsPair pair : this){
335             result.add(pair);
336         }
337
338         return result;
339     }
340
341     /**
342      * PAIRにアクセスするための反復子を提供する。
343      * この反復子での削除作業はできない。
344      * PAIR出現順序は未定義。
345      * @return 反復子イテレータ
346      */
347     public Iterator<JsPair> iterator(){
348         return UnmodIterator.unmodIterator(this.pairCollection);
349     }
350
351     /**
352      * 文字列表現を返す。
353      * JSON表記の全体もしくは一部としての利用も可能。
354      * @return {@inheritDoc}
355      */
356     @Override
357     public String toString(){
358         StringBuilder text = new StringBuilder();
359
360         text.append("{");
361
362         boolean hasElem = false;
363         for(JsPair pair : this){
364             if(hasElem) text.append(',');
365             text.append(pair);
366             hasElem = true;
367         }
368
369         text.append("}");
370
371         return text.toString();
372     }
373
374 }