001/* Copyright 2016-2017 Clifton Labs 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * http://www.apache.org/licenses/LICENSE-2.0 006 * Unless required by applicable law or agreed to in writing, software 007 * distributed under the License is distributed on an "AS IS" BASIS, 008 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 009 * See the License for the specific language governing permissions and 010 * limitations under the License. */ 011package org.json.simple; 012 013import java.io.IOException; 014import java.io.StringWriter; 015import java.io.Writer; 016import java.math.BigDecimal; 017import java.util.ArrayList; 018import java.util.Collection; 019import java.util.Iterator; 020import java.util.Map; 021 022/** JsonArray is a common non-thread safe data format for a collection of data. The contents of a JsonArray are only 023 * validated as JSON values on serialization. Meaning all values added to a JsonArray must be recognized by the Jsoner 024 * for it to be a true 'JsonArray', so it is really a JsonableArrayList that will serialize to a JsonArray if all of 025 * its contents are valid JSON. 026 * @see Jsoner 027 * @since 2.0.0 */ 028public class JsonArray extends ArrayList<Object> implements Jsonable{ 029 /** The serialization version this class is compatible 030 * with. This value doesn't need to be incremented if and only if the only changes to occur were updating comments, 031 * updating javadocs, adding new 032 * fields to the class, changing the fields from static to non-static, or changing the fields from transient to non 033 * transient. All other changes require this number be incremented. */ 034 private static final long serialVersionUID = 1L; 035 036 /** Instantiates an empty JsonArray. */ 037 public JsonArray(){ 038 super(); 039 } 040 041 /** Instantiate a new JsonArray using ArrayList's constructor of the same type. 042 * @param collection represents the elements to produce the JsonArray with. */ 043 public JsonArray(final Collection<?> collection){ 044 super(collection); 045 } 046 047 /** A convenience method that assumes every element of the JsonArray is castable to T before adding it to a 048 * collection of Ts. 049 * @param <T> represents the type that all of the elements of the JsonArray should be cast to and the type the 050 * collection will contain. 051 * @param destination represents where all of the elements of the JsonArray are added to after being cast to the 052 * generic type 053 * provided. 054 * @throws ClassCastException if the unchecked cast of an element to T fails. */ 055 @SuppressWarnings("unchecked") 056 public <T> void asCollection(final Collection<T> destination){ 057 for(final Object o : this){ 058 destination.add((T)o); 059 } 060 } 061 062 /** A convenience method that assumes there is a BigDecimal, Number, or String at the given index. If a Number or 063 * String is there it is used to construct a new BigDecimal. 064 * @param index representing where the value is expected to be at. 065 * @return the value stored at the key or the default provided if the key doesn't exist. 066 * @throws ClassCastException if there was a value but didn't match the assumed return types. 067 * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. 068 * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal. 069 * @see BigDecimal 070 * @see Number#doubleValue() */ 071 public BigDecimal getBigDecimal(final int index){ 072 Object returnable = this.get(index); 073 if(returnable instanceof BigDecimal){ 074 /* Success there was a BigDecimal. */ 075 }else if(returnable instanceof Number){ 076 /* A number can be used to construct a BigDecimal. */ 077 returnable = new BigDecimal(returnable.toString()); 078 }else if(returnable instanceof String){ 079 /* A number can be used to construct a BigDecimal. */ 080 returnable = new BigDecimal((String)returnable); 081 } 082 return (BigDecimal)returnable; 083 } 084 085 /** A convenience method that assumes there is a Boolean or String value at the given index. 086 * @param index represents where the value is expected to be at. 087 * @return the value at the index provided cast to a boolean. 088 * @throws ClassCastException if there was a value but didn't match the assumed return type. 089 * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. */ 090 public Boolean getBoolean(final int index){ 091 Object returnable = this.get(index); 092 if(returnable instanceof String){ 093 returnable = Boolean.valueOf((String)returnable); 094 } 095 return (Boolean)returnable; 096 } 097 098 /** A convenience method that assumes there is a Number or String value at the given index. 099 * @param index represents where the value is expected to be at. 100 * @return the value at the index provided cast to a byte. 101 * @throws ClassCastException if there was a value but didn't match the assumed return type. 102 * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number 103 * represents the double or float Infinity or NaN. 104 * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. 105 * @see Number */ 106 public Byte getByte(final int index){ 107 Object returnable = this.get(index); 108 if(returnable == null){ 109 return null; 110 } 111 if(returnable instanceof String){ 112 /* A String can be used to construct a BigDecimal. */ 113 returnable = new BigDecimal((String)returnable); 114 } 115 return ((Number)returnable).byteValue(); 116 } 117 118 /** A convenience method that assumes there is a Collection value at the given index. 119 * @param <T> the kind of collection to expect at the index. Note unless manually added, collection values will be a 120 * JsonArray. 121 * @param index represents where the value is expected to be at. 122 * @return the value at the index provided cast to a Collection. 123 * @throws ClassCastException if there was a value but didn't match the assumed return type. 124 * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. 125 * @see Collection */ 126 @SuppressWarnings("unchecked") 127 public <T extends Collection<?>> T getCollection(final int index){ 128 /* The unchecked warning is suppressed because there is no way of guaranteeing at compile time the cast will 129 * work. */ 130 return (T)this.get(index); 131 } 132 133 /** A convenience method that assumes there is a Number or String value at the given index. 134 * @param index represents where the value is expected to be at. 135 * @return the value at the index provided cast to a double. 136 * @throws ClassCastException if there was a value but didn't match the assumed return type. 137 * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number 138 * represents the double or float Infinity or NaN. 139 * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. 140 * @see Number */ 141 public Double getDouble(final int index){ 142 Object returnable = this.get(index); 143 if(returnable == null){ 144 return null; 145 } 146 if(returnable instanceof String){ 147 /* A String can be used to construct a BigDecimal. */ 148 returnable = new BigDecimal((String)returnable); 149 } 150 return ((Number)returnable).doubleValue(); 151 } 152 153 /** A convenience method that assumes there is a String value at the given index representing a fully qualified name 154 * in dot notation of an enum. 155 * @param index representing where the value is expected to be at. 156 * @param <T> the Enum type the value at the index is expected to belong to. 157 * @return the enum based on the string found at the index, or null if the value at the index was null. 158 * @throws ClassNotFoundException if the element was a String but the declaring enum type couldn't be determined 159 * with it. 160 * @throws ClassCastException if the element at the index was not a String or if the fully qualified enum name is of 161 * the wrong type. 162 * @throws IllegalArgumentException if an enum type was dynamically determined but it doesn't define an enum with 163 * the dynamically determined name. 164 * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. 165 * @see Enum#valueOf(Class, String) 166 * @deprecated 2.3.0 Jsoner deprecated automatically serializing enums as Strings. */ 167 @Deprecated 168 @SuppressWarnings("unchecked") 169 public <T extends Enum<T>> T getEnum(final int index) throws ClassNotFoundException{ 170 /* Supressing the unchecked warning because the returnType is dynamically identified and could lead to a 171 * ClassCastException when returnType is cast to Class<T>, which is expected by the method's contract. */ 172 T returnable; 173 final String element; 174 final String[] splitValues; 175 final int numberOfValues; 176 final StringBuilder returnTypeName; 177 final StringBuilder enumName; 178 final Class<T> returnType; 179 /* Make sure the element at the index is a String. */ 180 element = this.getString(index); 181 if(element == null){ 182 return null; 183 } 184 /* Get the package, class, and enum names. */ 185 splitValues = element.split("\\."); 186 numberOfValues = splitValues.length; 187 returnTypeName = new StringBuilder(); 188 enumName = new StringBuilder(); 189 for(int i = 0; i < numberOfValues; i++){ 190 if(i == (numberOfValues - 1)){ 191 /* If it is the last split value then it should be the name of the Enum since dots are not allowed in 192 * enum names. */ 193 enumName.append(splitValues[i]); 194 }else if(i == (numberOfValues - 2)){ 195 /* If it is the penultimate split value then it should be the end of the package/enum type and not need 196 * a dot appended to it. */ 197 returnTypeName.append(splitValues[i]); 198 }else{ 199 /* Must be part of the package/enum type and will need a dot appended to it since they got removed in 200 * the split. */ 201 returnTypeName.append(splitValues[i]); 202 returnTypeName.append("."); 203 } 204 } 205 /* Use the package/class and enum names to get the Enum<T>. */ 206 returnType = (Class<T>)Class.forName(returnTypeName.toString()); 207 returnable = Enum.valueOf(returnType, enumName.toString()); 208 return returnable; 209 } 210 211 /** A convenience method that assumes there is a Number or String value at the given index. 212 * @param index represents where the value is expected to be at. 213 * @return the value at the index provided cast to a float. 214 * @throws ClassCastException if there was a value but didn't match the assumed return type. 215 * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number 216 * represents the double or float Infinity or NaN. 217 * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. 218 * @see Number */ 219 public Float getFloat(final int index){ 220 Object returnable = this.get(index); 221 if(returnable == null){ 222 return null; 223 } 224 if(returnable instanceof String){ 225 /* A String can be used to construct a BigDecimal. */ 226 returnable = new BigDecimal((String)returnable); 227 } 228 return ((Number)returnable).floatValue(); 229 } 230 231 /** A convenience method that assumes there is a Number or String value at the given index. 232 * @param index represents where the value is expected to be at. 233 * @return the value at the index provided cast to a int. 234 * @throws ClassCastException if there was a value but didn't match the assumed return type. 235 * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number 236 * represents the double or float Infinity or NaN. 237 * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. 238 * @see Number */ 239 public Integer getInteger(final int index){ 240 Object returnable = this.get(index); 241 if(returnable == null){ 242 return null; 243 } 244 if(returnable instanceof String){ 245 /* A String can be used to construct a BigDecimal. */ 246 returnable = new BigDecimal((String)returnable); 247 } 248 return ((Number)returnable).intValue(); 249 } 250 251 /** A convenience method that assumes there is a Number or String value at the given index. 252 * @param index represents where the value is expected to be at. 253 * @return the value at the index provided cast to a long. 254 * @throws ClassCastException if there was a value but didn't match the assumed return type. 255 * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number 256 * represents the double or float Infinity or NaN. 257 * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. 258 * @see Number */ 259 public Long getLong(final int index){ 260 Object returnable = this.get(index); 261 if(returnable == null){ 262 return null; 263 } 264 if(returnable instanceof String){ 265 /* A String can be used to construct a BigDecimal. */ 266 returnable = new BigDecimal((String)returnable); 267 } 268 return ((Number)returnable).longValue(); 269 } 270 271 /** A convenience method that assumes there is a Map value at the given index. 272 * @param <T> the kind of map to expect at the index. Note unless manually added, Map values will be a JsonObject. 273 * @param index represents where the value is expected to be at. 274 * @return the value at the index provided cast to a Map. 275 * @throws ClassCastException if there was a value but didn't match the assumed return type. 276 * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. 277 * @see Map */ 278 @SuppressWarnings("unchecked") 279 public <T extends Map<?, ?>> T getMap(final int index){ 280 /* The unchecked warning is suppressed because there is no way of guaranteeing at compile time the cast will 281 * work. */ 282 return (T)this.get(index); 283 } 284 285 /** A convenience method that assumes there is a Number or String value at the given index. 286 * @param index represents where the value is expected to be at. 287 * @return the value at the index provided cast to a short. 288 * @throws ClassCastException if there was a value but didn't match the assumed return type. 289 * @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number 290 * represents the double or float Infinity or NaN. 291 * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. 292 * @see Number */ 293 public Short getShort(final int index){ 294 Object returnable = this.get(index); 295 if(returnable == null){ 296 return null; 297 } 298 if(returnable instanceof String){ 299 /* A String can be used to construct a BigDecimal. */ 300 returnable = new BigDecimal((String)returnable); 301 } 302 return ((Number)returnable).shortValue(); 303 } 304 305 /** A convenience method that assumes there is a Boolean, Number, or String value at the given index. 306 * @param index represents where the value is expected to be at. 307 * @return the value at the index provided cast to a String. 308 * @throws ClassCastException if there was a value but didn't match the assumed return type. 309 * @throws IndexOutOfBoundsException if the index is outside of the range of element indexes in the JsonArray. */ 310 public String getString(final int index){ 311 Object returnable = this.get(index); 312 if(returnable instanceof Boolean){ 313 returnable = returnable.toString(); 314 }else if(returnable instanceof Number){ 315 returnable = returnable.toString(); 316 } 317 return (String)returnable; 318 } 319 320 /* (non-Javadoc) 321 * @see org.json.simple.Jsonable#asJsonString() */ 322 @Override 323 public String toJson(){ 324 final StringWriter writable = new StringWriter(); 325 try{ 326 this.toJson(writable); 327 }catch(final IOException caught){ 328 /* See java.io.StringWriter. */ 329 } 330 return writable.toString(); 331 } 332 333 /* (non-Javadoc) 334 * @see org.json.simple.Jsonable#toJsonString(java.io.Writer) */ 335 @Override 336 public void toJson(final Writer writable) throws IOException{ 337 boolean isFirstElement = true; 338 final Iterator<Object> elements = this.iterator(); 339 writable.write('['); 340 while(elements.hasNext()){ 341 if(isFirstElement){ 342 isFirstElement = false; 343 }else{ 344 writable.write(','); 345 } 346 writable.write(Jsoner.serialize(elements.next())); 347 } 348 writable.write(']'); 349 } 350}