OSDN Git Service

df80a51e6c4d46b844c5e9a0b58c6cb4c53eed57
[jovsonz/Jovsonz.git] / src / main / java / jp / sourceforge / jovsonz / JsArray.java
1 /*
2  * JSON array 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.Iterator;
12 import java.util.LinkedList;
13 import java.util.List;
14
15 /**
16  * JSON ARRAY型Valueを表す。
17  * 子要素の配列リストを反映する。
18  * <h1>表記例</h1>
19  * <pre>
20  * [
21  *     true ,
22  *     "ABC" ,
23  *     12.3
24  * ]
25  * </pre>
26  */
27 public class JsArray
28         implements JsComposition<JsValue> {
29
30     private static final String ERRMSG_NOARRAYCOMMA =
31             "missing comma in ARRAY";
32     private static final String ERRMSG_NOELEM =
33             "missing element in ARRAY";
34
35     private final List<JsValue> valueList = new LinkedList<JsValue>();
36     private boolean changed = false;
37
38     /**
39      * コンストラクタ。
40      */
41     public JsArray(){
42         super();
43         return;
44     }
45
46     /**
47      * JSON文字列ソースからARRAY型Valueを読み込む。
48      * さらに子Valueへとパース解析が進む可能性がある。
49      * 別型の可能性のある先頭文字を読み込んだ場合、
50      * ソースに文字を読み戻した後nullが返される。
51      * @param source 文字列ソース
52      * @return ARRAY型Value。別型の可能性がある場合はnull。
53      * @throws IOException 入力エラー
54      * @throws JsParseException 不正な表現または意図しない入力終了
55      */
56     static JsArray parseArray(JsonSource source)
57             throws IOException, JsParseException {
58         char charHead = source.readOrDie();
59         if(charHead != '['){
60             source.unread(charHead);
61             return null;
62         }
63
64         JsArray result = new JsArray();
65
66         for(;;){
67             source.skipWhiteSpace();
68             char chData = source.readOrDie();
69             if(chData == ']') break;
70
71             if(result.isEmpty()){
72                 source.unread(chData);
73             }else{
74                 if(chData != ','){
75                     throw new JsParseException(ERRMSG_NOARRAYCOMMA,
76                                                source.getLineNumber() );
77                 }
78             }
79
80             JsValue value = Json.parseValue(source);
81             if(value == null){
82                 throw new JsParseException(ERRMSG_NOELEM,
83                                            source.getLineNumber() );
84             }
85
86             result.add(value);
87         }
88
89         return result;
90     }
91
92     /**
93      * {@inheritDoc}
94      * 常に{@link JsTypes#ARRAY}を返す。
95      * @return {@inheritDoc}
96      */
97     @Override
98     public JsTypes getJsTypes(){
99         return JsTypes.ARRAY;
100     }
101
102     /**
103      * このValueおよび子孫に変更があったか判定する。
104      * 子要素の追加・削除が行われたか、
105      * もしくは子要素のいずれかに変更が認められれば、
106      * このARRAY型Valueに変更があったとみなされる。
107      * @return {@inheritDoc}
108      */
109     @Override
110     public boolean hasChanged(){
111         if(this.changed) return true;
112
113         for(JsValue value : this.valueList){
114             if( ! (value instanceof JsComposition) ) continue;
115             JsComposition composition = (JsComposition) value;
116             if(composition.hasChanged()) return true;
117         }
118
119         return false;
120     }
121
122     /**
123      * このValueおよび子孫に変更がなかったことにする。
124      */
125     @Override
126     public void setUnchanged(){
127         this.changed = false;
128
129         for(JsValue value : this.valueList){
130             if( ! (value instanceof JsComposition) ) continue;
131             JsComposition composition = (JsComposition) value;
132             composition.setUnchanged();
133         }
134
135         return;
136     }
137
138     /**
139      * 深さ優先探索を行い各種構造の出現をビジターに通知する。
140      * thisを通知した後、子Valueを順に訪問し、最後に閉じ括弧を通知する。
141      * @param visitor {@inheritDoc}
142      * @throws JsVisitException {@inheritDoc}
143      */
144     @Override
145     public void traverse(ValueVisitor visitor) throws JsVisitException{
146         visitor.visitValue(this);
147
148         for(JsValue value : this.valueList){
149             value.traverse(visitor);
150         }
151
152         visitor.visitCompositionClose(this);
153
154         return;
155     }
156
157     /**
158      * 配列要素数を返す。
159      * @return {@inheritDoc}
160      */
161     @Override
162     public int size(){
163         return this.valueList.size();
164     }
165
166     /**
167      * 配列が空か判定する。
168      * @return {@inheritDoc}
169      */
170     @Override
171     public boolean isEmpty(){
172         return this.valueList.isEmpty();
173     }
174
175     /**
176      * 配列を空にする。
177      */
178     @Override
179     public void clear(){
180         if(this.valueList.size() > 0) this.changed = true;
181         this.valueList.clear();
182         return;
183     }
184
185     /**
186      * ハッシュ値を返す。
187      * 全ての子孫Valueのハッシュ値からその都度合成される。高コスト注意!。
188      * @return {@inheritDoc}
189      * @see java.util.List#hashCode()
190      */
191     @Override
192     public int hashCode(){
193         return this.valueList.hashCode();
194     }
195
196     /**
197      * 等価判定を行う。
198      * 双方の配列サイズが一致し
199      * その全ての子Valueでのequals()が等価と判断された場合のみ
200      * 等価と判断される。
201      * @param obj {@inheritDoc}
202      * @return {@inheritDoc}
203      * @see java.util.List#equals(Object)
204      */
205     @Override
206     public boolean equals(Object obj){
207         if(this == obj) return true;
208
209         if( ! (obj instanceof JsArray) ) return false;
210         JsArray array = (JsArray) obj;
211
212         return this.valueList.equals(array.valueList);
213     }
214
215     /**
216      * 配列にValueを追加する。
217      * 同じJsValueインスタンスを複数回追加することも可能。
218      * @param value JSON Value
219      * @throws NullPointerException 引数がnull
220      */
221     public void add(JsValue value) throws NullPointerException{
222         if(value == null) throw new NullPointerException();
223         this.valueList.add(value);
224         this.changed = true;
225         return;
226     }
227
228     /**
229      * 配列から指定された位置のValueを返す。
230      * @param index 0で始まる配列上の位置
231      * @return Value JSON Value
232      * @throws IndexOutOfBoundsException 不正な位置指定
233      */
234     public JsValue get(int index) throws IndexOutOfBoundsException{
235         return this.valueList.get(index);
236     }
237
238     /**
239      * 配列からValueを削除する。
240      * {@link java.util.List#remove(Object)}と異なり、
241      * 削除対象の検索に際して
242      * {@link java.lang.Object#equals(Object)}は使われない。
243      * 一致するインスタンスが複数存在する場合、
244      * 先頭に近いインスタンスのみ削除される。
245      * 一致するインスタンスが存在しなければなにもしない。
246      * @param value JSON Value
247      * @return 既存のValueが削除されたならtrue
248      */
249     // TODO 必要?
250     public boolean remove(JsValue value){
251         boolean removed = false;
252
253         Iterator<JsValue> it = this.valueList.iterator();
254         while(it.hasNext()){
255             JsValue elem = it.next();
256             if(elem == value){
257                 it.remove();
258                 this.changed = true;
259                 removed = true;
260                 break;
261             }
262         }
263
264         return removed;
265     }
266
267     /**
268      * 配列から指定位置のValueを削除する。
269      * @param index 0で始まる削除対象のインデックス値
270      * @return 削除されたValue
271      * @throws IndexOutOfBoundsException 不正なインデックス値
272      */
273     public JsValue remove(int index) throws IndexOutOfBoundsException{
274         JsValue removed = this.valueList.remove(index);
275         this.changed = true;
276         return removed;
277     }
278
279     /**
280      * Valueにアクセスするための反復子を提供する。
281      * この反復子での削除作業はできない。
282      * @return 反復子イテレータ
283      * @see UnmodIterator
284      */
285     public Iterator<JsValue> iterator(){
286         return UnmodIterator.unmodIterator(this.valueList);
287     }
288
289     /**
290      * {@inheritDoc}
291      * 文字列表現を返す。
292      * JSON表記の全体もしくは一部としての利用も可能。
293      * @return {@inheritDoc}
294      */
295     @Override
296     public String toString(){
297         StringBuilder text = new StringBuilder();
298
299         text.append("[");
300         boolean hasElem = false;
301         for(JsValue value : this.valueList){
302             if(hasElem) text.append(',');
303             text.append(value);
304             hasElem = true;
305         }
306         text.append("]");
307
308         return text.toString();
309     }
310
311 }