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