001/*******************************************************************************
002 * Copyright (c) 2013, 2015 EclipseSource.
003 *
004 * Permission is hereby granted, free of charge, to any person obtaining a copy
005 * of this software and associated documentation files (the "Software"), to deal
006 * in the Software without restriction, including without limitation the rights
007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
008 * copies of the Software, and to permit persons to whom the Software is
009 * furnished to do so, subject to the following conditions:
010 *
011 * The above copyright notice and this permission notice shall be included in all
012 * copies or substantial portions of the Software.
013 *
014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
020 * SOFTWARE.
021 ******************************************************************************/
022package com.restfb.json;
023
024import java.io.IOException;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Iterator;
028import java.util.List;
029
030/**
031 * Represents a JSON array, an ordered collection of JSON values.
032 * <p>
033 * Elements can be added using the <code>add(...)</code> methods which accept instances of {@link JsonValue}, strings,
034 * primitive numbers, and boolean values. To replace an element of an array, use the <code>set(int, ...)</code> methods.
035 * </p>
036 * <p>
037 * Elements can be accessed by their index using {@link #get(int)}. This class also supports iterating over the elements
038 * in document order using an {@link #iterator()} or an enhanced for loop:
039 * </p>
040 * 
041 * <pre>
042 * for (JsonValue value : jsonArray) {
043 *   ...
044 * }
045 * </pre>
046 * <p>
047 * An equivalent {@link List} can be obtained from the method {@link #values()}.
048 * </p>
049 * <p>
050 * Note that this class is <strong>not thread-safe</strong>. If multiple threads access a <code>JsonArray</code>
051 * instance concurrently, while at least one of these threads modifies the contents of this array, access to the
052 * instance must be synchronized externally. Failure to do so may lead to an inconsistent state.
053 * </p>
054 * <p>
055 * This class is <strong>not supposed to be extended</strong> by clients.
056 * </p>
057 */
058@SuppressWarnings("serial") // use default serial UID
059public class JsonArray extends JsonValue implements Iterable<JsonValue> {
060
061  private final List<JsonValue> values;
062
063  // String constants
064  private static final String ARRAY_IS_NULL = "array is null";
065
066  /**
067   * Creates a new empty JsonArray.
068   */
069  public JsonArray() {
070    values = new ArrayList<>();
071  }
072
073  /**
074   * Creates a new JsonArray with the contents of the specified JSON array.
075   *
076   * @param array
077   *          the JsonArray to get the initial contents from, must not be <code>null</code>
078   */
079  public JsonArray(JsonArray array) {
080    this(array, false);
081  }
082
083  private JsonArray(JsonArray array, boolean unmodifiable) {
084    if (array == null) {
085      throw new NullPointerException(ARRAY_IS_NULL);
086    }
087    if (unmodifiable) {
088      values = Collections.unmodifiableList(array.values);
089    } else {
090      values = new ArrayList<>(array.values);
091    }
092  }
093
094  /**
095   * Returns an unmodifiable wrapper for the specified JsonArray. This method allows to provide read-only access to a
096   * JsonArray.
097   * <p>
098   * The returned JsonArray is backed by the given array and reflects subsequent changes. Attempts to modify the
099   * returned JsonArray result in an <code>UnsupportedOperationException</code>.
100   * </p>
101   *
102   * @param array
103   *          the JsonArray for which an unmodifiable JsonArray is to be returned
104   * @return an unmodifiable view of the specified JsonArray
105   */
106  public static JsonArray unmodifiableArray(JsonArray array) {
107    return new JsonArray(array, true);
108  }
109
110  /**
111   * Appends the JSON representation of the specified <code>int</code> value to the end of this array.
112   *
113   * @param value
114   *          the value to add to the array
115   * @return the array itself, to enable method chaining
116   */
117  public JsonArray add(int value) {
118    values.add(Json.value(value));
119    return this;
120  }
121
122  /**
123   * Appends the JSON representation of the specified <code>long</code> value to the end of this array.
124   *
125   * @param value
126   *          the value to add to the array
127   * @return the array itself, to enable method chaining
128   */
129  public JsonArray add(long value) {
130    values.add(Json.value(value));
131    return this;
132  }
133
134  /**
135   * Appends the JSON representation of the specified <code>float</code> value to the end of this array.
136   *
137   * @param value
138   *          the value to add to the array
139   * @return the array itself, to enable method chaining
140   */
141  public JsonArray add(float value) {
142    values.add(Json.value(value));
143    return this;
144  }
145
146  /**
147   * Appends the JSON representation of the specified <code>double</code> value to the end of this array.
148   *
149   * @param value
150   *          the value to add to the array
151   * @return the array itself, to enable method chaining
152   */
153  public JsonArray add(double value) {
154    values.add(Json.value(value));
155    return this;
156  }
157
158  /**
159   * Appends the JSON representation of the specified <code>boolean</code> value to the end of this array.
160   *
161   * @param value
162   *          the value to add to the array
163   * @return the array itself, to enable method chaining
164   */
165  public JsonArray add(boolean value) {
166    values.add(Json.value(value));
167    return this;
168  }
169
170  /**
171   * Appends the JSON representation of the specified string to the end of this array.
172   *
173   * @param value
174   *          the string to add to the array
175   * @return the array itself, to enable method chaining
176   */
177  public JsonArray add(String value) {
178    values.add(Json.value(value));
179    return this;
180  }
181
182  /**
183   * Appends the specified JSON value to the end of this array.
184   *
185   * @param value
186   *          the JsonValue to add to the array, must not be <code>null</code>
187   * @return the array itself, to enable method chaining
188   */
189  public JsonArray add(JsonValue value) {
190    if (value == null) {
191      throw new NullPointerException("value is null");
192    }
193    values.add(value);
194    return this;
195  }
196
197  /**
198   * Replaces the element at the specified position in this array with the JSON representation of the specified
199   * <code>int</code> value.
200   *
201   * @param index
202   *          the index of the array element to replace
203   * @param value
204   *          the value to be stored at the specified array position
205   * @return the array itself, to enable method chaining
206   * @throws IndexOutOfBoundsException
207   *           if the index is out of range, i.e. <code>index &lt; 0</code> or <code>index &gt;= size</code>
208   */
209  public JsonArray set(int index, int value) {
210    values.set(index, Json.value(value));
211    return this;
212  }
213
214  /**
215   * Replaces the element at the specified position in this array with the JSON representation of the specified
216   * <code>long</code> value.
217   *
218   * @param index
219   *          the index of the array element to replace
220   * @param value
221   *          the value to be stored at the specified array position
222   * @return the array itself, to enable method chaining
223   * @throws IndexOutOfBoundsException
224   *           if the index is out of range, i.e. <code>index &lt; 0</code> or <code>index &gt;= size</code>
225   */
226  public JsonArray set(int index, long value) {
227    values.set(index, Json.value(value));
228    return this;
229  }
230
231  /**
232   * Replaces the element at the specified position in this array with the JSON representation of the specified
233   * <code>float</code> value.
234   *
235   * @param index
236   *          the index of the array element to replace
237   * @param value
238   *          the value to be stored at the specified array position
239   * @return the array itself, to enable method chaining
240   * @throws IndexOutOfBoundsException
241   *           if the index is out of range, i.e. <code>index &lt; 0</code> or <code>index &gt;= size</code>
242   */
243  public JsonArray set(int index, float value) {
244    values.set(index, Json.value(value));
245    return this;
246  }
247
248  /**
249   * Replaces the element at the specified position in this array with the JSON representation of the specified
250   * <code>double</code> value.
251   *
252   * @param index
253   *          the index of the array element to replace
254   * @param value
255   *          the value to be stored at the specified array position
256   * @return the array itself, to enable method chaining
257   * @throws IndexOutOfBoundsException
258   *           if the index is out of range, i.e. <code>index &lt; 0</code> or <code>index &gt;= size</code>
259   */
260  public JsonArray set(int index, double value) {
261    values.set(index, Json.value(value));
262    return this;
263  }
264
265  /**
266   * Replaces the element at the specified position in this array with the JSON representation of the specified
267   * <code>boolean</code> value.
268   *
269   * @param index
270   *          the index of the array element to replace
271   * @param value
272   *          the value to be stored at the specified array position
273   * @return the array itself, to enable method chaining
274   * @throws IndexOutOfBoundsException
275   *           if the index is out of range, i.e. <code>index &lt; 0</code> or <code>index &gt;= size</code>
276   */
277  public JsonArray set(int index, boolean value) {
278    values.set(index, Json.value(value));
279    return this;
280  }
281
282  /**
283   * Replaces the element at the specified position in this array with the JSON representation of the specified string.
284   *
285   * @param index
286   *          the index of the array element to replace
287   * @param value
288   *          the string to be stored at the specified array position
289   * @return the array itself, to enable method chaining
290   * @throws IndexOutOfBoundsException
291   *           if the index is out of range, i.e. <code>index &lt; 0</code> or <code>index &gt;= size</code>
292   */
293  public JsonArray set(int index, String value) {
294    values.set(index, Json.value(value));
295    return this;
296  }
297
298  /**
299   * Replaces the element at the specified position in this array with the specified JSON value.
300   *
301   * @param index
302   *          the index of the array element to replace
303   * @param value
304   *          the value to be stored at the specified array position, must not be <code>null</code>
305   * @return the array itself, to enable method chaining
306   * @throws IndexOutOfBoundsException
307   *           if the index is out of range, i.e. <code>index &lt; 0</code> or <code>index &gt;= size</code>
308   */
309  public JsonArray set(int index, JsonValue value) {
310    if (value == null) {
311      throw new NullPointerException("value is null");
312    }
313    values.set(index, value);
314    return this;
315  }
316
317  /**
318   * Removes the element at the specified index from this array.
319   *
320   * @param index
321   *          the index of the element to remove
322   * @return the array itself, to enable method chaining
323   * @throws IndexOutOfBoundsException
324   *           if the index is out of range, i.e. <code>index &lt; 0</code> or <code>index &gt;= size</code>
325   */
326  public JsonArray remove(int index) {
327    values.remove(index);
328    return this;
329  }
330
331  /**
332   * Returns the number of elements in this array.
333   *
334   * @return the number of elements in this array
335   */
336  public int size() {
337    return values.size();
338  }
339
340  /**
341   * Returns <code>true</code> if this array contains no elements.
342   *
343   * @return <code>true</code> if this array contains no elements
344   */
345  public boolean isEmpty() {
346    return values.isEmpty();
347  }
348
349  /**
350   * Returns the value of the element at the specified position in this array.
351   *
352   * @param index
353   *          the index of the array element to return
354   * @return the value of the element at the specified position
355   * @throws IndexOutOfBoundsException
356   *           if the index is out of range, i.e. <code>index &lt; 0</code> or <code>index &gt;= size</code>
357   */
358  public JsonValue get(int index) {
359    return values.get(index);
360  }
361
362  /**
363   * Returns a list of the values in this array in document order. The returned list is backed by this array and will
364   * reflect subsequent changes. It cannot be used to modify this array. Attempts to modify the returned list will
365   * result in an exception.
366   *
367   * @return a list of the values in this array
368   */
369  public List<JsonValue> values() {
370    return Collections.unmodifiableList(values);
371  }
372
373  /**
374   * Returns an iterator over the values of this array in document order. The returned iterator cannot be used to modify
375   * this array.
376   *
377   * @return an iterator over the values of this array
378   */
379  public Iterator<JsonValue> iterator() {
380    final Iterator<JsonValue> iterator = values.iterator();
381    return new Iterator<JsonValue>() {
382
383      public boolean hasNext() {
384        return iterator.hasNext();
385      }
386
387      public JsonValue next() {
388        return iterator.next();
389      }
390
391      public void remove() {
392        throw new UnsupportedOperationException();
393      }
394    };
395  }
396
397  @Override
398  void write(JsonWriter writer) throws IOException {
399    writer.writeArrayOpen();
400    Iterator<JsonValue> iterator = iterator();
401    if (iterator.hasNext()) {
402      iterator.next().write(writer);
403      while (iterator.hasNext()) {
404        writer.writeArraySeparator();
405        iterator.next().write(writer);
406      }
407    }
408    writer.writeArrayClose();
409  }
410
411  @Override
412  public boolean isArray() {
413    return true;
414  }
415
416  @Override
417  public JsonArray asArray() {
418    return this;
419  }
420
421  @Override
422  public int hashCode() {
423    return values.hashCode();
424  }
425
426
427  /**
428   * Indicates whether a given object is "equal to" this JsonArray. An object is considered equal
429   * if it is also a <code>JsonArray</code> and both arrays contain the same list of values.
430   * <p>
431   * If two JsonArrays are equal, they will also produce the same JSON output.
432   * </p>
433   *
434   * @param object
435   *          the object to be compared with this JsonArray
436   * @return <tt>true</tt> if the specified object is equal to this JsonArray, <code>false</code>
437   *         otherwise
438   */
439  @Override
440  public boolean equals(Object object) {
441    if (this == object) {
442      return true;
443    }
444    if (object == null) {
445      return false;
446    }
447    if (getClass() != object.getClass()) {
448      return false;
449    }
450    JsonArray other = (JsonArray) object;
451    return values.equals(other.values);
452  }
453
454}