4 * License : The MIT License
5 * Copyright(c) 2009 olyutorskii
8 package jp.sourceforge.jovsonz;
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;
17 import java.util.TreeMap;
20 * JSON OBJECT型Valueを表す。
21 * PAIR名と子要素の組(PAIR)の集合を反映する。
22 * PAIR名の並び順に関しては未定義とする。
32 implements JsComposition<JsPair> {
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";
43 private final Map<String, JsPair> pairMap =
44 new TreeMap<String, JsPair>();
45 private final Collection<JsPair> pairCollection = this.pairMap.values();
47 private boolean changed = false;
59 * JSON文字列ソースからOBJECT型Valueを読み込む。
60 * さらに子Valueへとパース解析が進む可能性がある。
61 * 別型の可能性のある先頭文字を読み込んだ場合、
62 * ソースに文字を読み戻した後nullが返される。
63 * @param source 文字列ソース
64 * @return OBJECT型Value。別型の可能性がある場合はnull。
65 * @throws IOException 入力エラー
66 * @throws JsParseException 不正な表記もしくは意図しない入力終了
68 static JsObject parseObject(JsonSource source)
69 throws IOException, JsParseException{
70 char charHead = source.readOrDie();
72 source.unread(charHead);
76 JsObject result = new JsObject();
79 source.skipWhiteSpace();
80 char chData = source.readOrDie();
81 if(chData == '}') break;
84 source.unread(chData);
87 throw new JsParseException(ERRMSG_NOOBJECTCOMMA,
88 source.getLineNumber() );
90 source.skipWhiteSpace();
93 JsString name = JsString.parseString(source);
95 throw new JsParseException(ERRMSG_NOHASHNAME,
96 source.getLineNumber() );
99 source.skipWhiteSpace();
100 chData = source.readOrDie();
102 throw new JsParseException(ERRMSG_NOHASHSEP,
103 source.getLineNumber() );
106 JsValue value = Json.parseValue(source);
108 throw new JsParseException(ERRMSG_NOHASHVAL,
109 source.getLineNumber() );
112 result.putValue(name.toRawString(), value);
120 * 常に{@link JsTypes#OBJECT}を返す。
121 * @return {@inheritDoc}
124 public JsTypes getJsTypes(){
125 return JsTypes.OBJECT;
129 * このValueおよび子孫に変更があったか判定する。
131 * もしくはPAIRのValue値いずれかに変更が認められれば、
132 * このOBJECT型Valueに変更があったとみなされる。
133 * @return {@inheritDoc}
136 public boolean hasChanged(){
137 if(this.changed) return true;
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;
150 * このValueおよび子孫に変更がなかったことにする。
153 public void setUnchanged(){
154 this.changed = false;
156 for(JsPair pair : this){
157 JsValue value = pair.getValue();
158 if( ! (value instanceof JsComposition) ) continue;
159 JsComposition composition = (JsComposition) value;
160 composition.setUnchanged();
167 * 深さ優先探索を行い各種構造の出現をビジターに通知する。
168 * thisを通知した後、PAIRの各名前およびValueを順に訪問し、
171 * @param visitor {@inheritDoc}
172 * @throws JsVisitException {@inheritDoc}
175 public void traverse(ValueVisitor visitor) throws JsVisitException{
176 visitor.visitValue(this);
178 for(JsPair pair : this){
179 String name = pair.getName();
180 JsValue value = pair.getValue();
181 visitor.visitPairName(name);
182 value.traverse(visitor);
185 visitor.visitCompositionClose(this);
196 return this.pairMap.size();
204 public boolean isEmpty(){
205 return this.pairMap.isEmpty();
213 if(this.pairMap.size() > 0) this.changed = true;
214 this.pairMap.clear();
220 * 全てのPAIRのハッシュ値からその都度合成される。高コスト注意!。
221 * @return {@inheritDoc}
224 public int hashCode(){
225 return this.pairMap.hashCode();
231 * 全てのPAIR名およびそれに対応付けられたValueが一致した場合のみ
233 * @param obj {@inheritDoc}
234 * @return {@inheritDoc}
237 public boolean equals(Object obj){
238 if(this == obj) return true;
240 if( ! (obj instanceof JsObject) ) return false;
241 JsObject composit = (JsObject) obj;
243 return this.pairMap.equals(composit.pairMap);
247 * 名前とValueからPAIRを登録する。
250 * @return 旧Value。同じ内容のPAIRがすでに存在していたらnull
251 * @throws NullPointerException 引数のいずれかがnull
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();
258 JsValue oldValue = null;
259 JsPair oldPair = this.pairMap.get(name);
261 oldValue = oldPair.getValue();
262 if(value.equals(oldValue)) return null;
265 JsPair newPair = new JsPair(name, value);
266 this.pairMap.put(name, newPair);
275 * @return 対応するValue。見つからなければnull
277 public JsValue getValue(String name){
278 JsPair pair = this.pairMap.get(name);
279 if(pair == null) return null;
280 JsValue value = pair.getValue();
286 * 同じPAIR名を持つPAIRは無条件に上書きされる。
289 public void putPair(JsPair pair){
290 this.pairMap.put(pair.getName(), pair);
297 * @return PAIR。見つからなければnull
299 public JsPair getPair(String name){
300 JsValue value = getValue(name);
301 if(value == null) return null;
303 return new JsPair(name, value);
309 * @return 消されたPAIR。該当するPAIRがなければnull
311 public JsPair remove(String name){
312 JsPair oldPair = this.pairMap.remove(name);
313 if(oldPair != null) this.changed = true;
319 * 保持する全PAIRのPAIR名の集合を返す。
322 public Set<String> nameSet(){
323 return this.pairMap.keySet();
328 * このリストを上書き操作しても影響はない。
331 public List<JsPair> getPairList(){
332 List<JsPair> result = new ArrayList<JsPair>(this.pairMap.size());
334 for(JsPair pair : this){
342 * PAIRにアクセスするための反復子を提供する。
347 public Iterator<JsPair> iterator(){
348 return UnmodIterator.unmodIterator(this.pairCollection);
353 * JSON表記の全体もしくは一部としての利用も可能。
354 * @return {@inheritDoc}
357 public String toString(){
358 StringBuilder text = new StringBuilder();
362 boolean hasElem = false;
363 for(JsPair pair : this){
364 if(hasElem) text.append(',');
371 return text.toString();