/*
* Copyright 2013 Simon Thiel
*
* This file is part of SitJar.
*
* SitJar is free software: you can redistribute it and/or modify
* it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SitJar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SitJar. If not, see <http://www.gnu.org/licenses/lgpl.txt>.
*/
/*
* @author Simon Thiel <simon.thiel at gmx.de>
*/
package sit.json;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author simon
*/
public class JSONObject implements Iterable<Map.Entry<String, JSONObject>> {
public static String JSON_MIME_TYPE = "application/json";
public final static int JSON_TYPE_OBJECT = 0;
public final static int JSON_TYPE_COLLECTION = 1;
public final static int JSON_TYPE_QUOTED_VALUE = 2;
public final static int JSON_TYPE_UNQUOTED_VALUE = 3;
private LinkedHashMap<String, JSONObject> children = null; // in case of object type JSONObject children containing the sub-objects/values in case of collection children contains <[i],<JSONObject>> with i:0..(n-1)
private String key;
private String value = null;
private int type = JSON_TYPE_OBJECT;
public JSONObject(String key) {
//System.out.println("new object:<"+key+">");
this.key = key;
}
public JSONObject(String key, String value, boolean hasQuotes) {
this.key = key;
this.setValue(value, hasQuotes);
}
/**
* manually set the type of the JSONObject
* generally this is done automatically - please make sure, you know what you're doing.
*
* valid types are JSONObject.JSON_TYPE_*
*
* @param type
*/
public void setType(int type) {
saveTypeSwitch(type);
}
public boolean isLeaf() {
return type == JSON_TYPE_QUOTED_VALUE || type == JSON_TYPE_UNQUOTED_VALUE;
}
public boolean isCollection() {
return type == JSON_TYPE_COLLECTION;
}
public boolean isObject() {
return type == JSON_TYPE_OBJECT;
}
/**
* @return the key
*/
public String getKey() {
return key;
}
/**
* @return the value
*/
public String getValue() {
return value;
}
public Vector<JSONObject> getItems() {
if (children == null) { //lazy instantiation
children = new LinkedHashMap();
}
return new Vector(this.children.values());
}
/**
* in case the JSONObject is a collection returns number of items in the
* collection otherwise -1
*
* @return
*/
public int getItemsSize() {
if (type != JSON_TYPE_COLLECTION) {
return -1;
}
if (children==null){
return 0;
}
return this.children.size();
}
private void saveTypeSwitch(int newType) {
if (this.type == newType) {
return;
}//ok type was switched
if (children != null) {
children.clear();
Logger.getLogger(getClass().getName()).log(Level.WARNING,
"switch of JSON-Object Type with existing child items:(old/new)\n" + this.type + "/" + newType);
}
if (value != null) {
Logger.getLogger(getClass().getName()).log(Level.WARNING,
"switch of JSON-Object Type with existing value:(oldType/newType/value)\n"
+ "" + this.type + "/" + newType + "/" + this.value);
this.value = null;
}
this.type = newType;
}
public JSONObject addChild(JSONObject child) {
saveTypeSwitch(JSON_TYPE_OBJECT);
if (children == null) { //lazy instantiation
children = new LinkedHashMap();
}
return this.children.put(child.getKey(), child);
}
private String arrayToString(String[] stringArray) {
String result = "[";
for (String item : stringArray) {
result += item + ",";
}
if (stringArray.length > 0) {
result = result.substring(0, result.length() - 1);
}
return result + "]";
}
public JSONObject getChild(String key) throws JSONPathAccessException {
String[] path = {key};
return getChild(path);
}
public JSONObject getChild(String[] keySequence) throws JSONPathAccessException {
if (keySequence.length < 1) {
return this;
}
if (isLeaf()) {
throw new JSONPathAccessException("Unable to proceed path at leaf:" + getKey() + " next element would have been:" + keySequence[0]);
}
if (isCollection()) {
try {
int index = Integer.parseInt(keySequence[0]);
return children.get("" + index).getChild(copyOfRange(keySequence, 1, keySequence.length));
} catch (NumberFormatException ex) {
throw new JSONPathAccessException("Unable to proceed path when trying to parse index of collection at collection:" + getKey() + " next element would have been:" + keySequence[0]);
} catch (IndexOutOfBoundsException ex) {
throw new JSONPathAccessException("Index out of bounds at collection:" + getKey() + " for index:" + keySequence[0]);
} catch (NullPointerException ex) {
throw new JSONPathAccessException("Collection:" + getKey() + " was not instantiated!");
}
}//else its an object
if ((children == null) || !children.containsKey(keySequence[0])) {
throw new JSONPathAccessException("Unable to proceed path from:" + getKey() + " no subobject found with key:" + keySequence[0]);
}
return children.get(keySequence[0]).getChild(copyOfRange(keySequence, 1, keySequence.length));
}
/***********************************************
* Workaround for usage with Android2.2 / Java < 6
* Offers all different copyOfRange Methods for convenience use
*
* Method Source from http://hg.openjdk.java.net/jdk7/jdk7/jdk/raw-file/bfd7abda8f79/src/share/classes/java/util/Arrays.java
*/
public static <T> T[] copyOfRange(T[] original, int from, int to) {
return copyOfRange(original, from, to, (Class<T[]>) original.getClass());
}
public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
public static byte[] copyOfRange(byte[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
byte[] copy = new byte[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
public static short[] copyOfRange(short[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
short[] copy = new short[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
public static int[] copyOfRange(int[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
int[] copy = new int[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
public static long[] copyOfRange(long[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
long[] copy = new long[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
public static char[] copyOfRange(char[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
char[] copy = new char[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
public static float[] copyOfRange(float[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
float[] copy = new float[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
public static double[] copyOfRange(double[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
double[] copy = new double[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
public static boolean[] copyOfRange(boolean[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
boolean[] copy = new boolean[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
//***********************************************
public void addItem(JSONObject item) {
saveTypeSwitch(JSON_TYPE_COLLECTION);
if (children == null) { //lazy instantiation
children = new LinkedHashMap();
}
String itemKey = "" + children.size();
item.setKey(itemKey);
this.children.put(itemKey, item);
}
public final void setValue(String value, boolean hasQuotes) {
if (hasQuotes) {
saveTypeSwitch(JSON_TYPE_QUOTED_VALUE);
} else {
saveTypeSwitch(JSON_TYPE_UNQUOTED_VALUE);
}
//System.out.println("["+key+"] setting value:<"+value+">");
this.value = value;
}
public String toJson() {
if (isLeaf()) {
if ((value != null) && (type == JSON_TYPE_QUOTED_VALUE)) {
return "\"" + JSONTextHelper.encodeText(value) + "\"";
} else {
return value;
}
} else if (isCollection()) {
if (children == null) {
return "[]";
}
String result = "[";
for (JSONObject object : children.values()) {
result += "" + object.toJson() + ",";
}
//remove the last comma if we had any entries
if (!children.isEmpty()) {
result = result.substring(0, result.length() - 1);
}
return result + "]";
} //else its assumed to be an object
if (children == null) {
return "{}";
}
String result = "{";
for (Entry<String, JSONObject> child : children.entrySet()) {
result += "\"" + child.getKey() + "\":" + child.getValue().toJson() + ", ";
}
//remove the last comma if we had any entries
if (!children.isEmpty()) {
result = result.substring(0, result.length() - 2);
}
return result + "}";
}
@Override
public String toString() {
String json = toJson();
StringBuilder result = new StringBuilder();
int bCount = 0;
boolean quoteFlag = false;
boolean escapeFlag = false;
boolean lineForwardFlag = true;
for (int i = 0; i < json.length(); i++) {
char chr = json.charAt(i);
quoteFlag = ((!quoteFlag) && (chr == '"'))
|| (quoteFlag && (chr != '"'))
|| (escapeFlag && quoteFlag);
if (!quoteFlag) {
if (chr == '{' || chr == '[') {
bCount++;
}
if (chr == '}' || chr == ']') {
bCount--;
}
}
if (!quoteFlag && (chr == '{' || chr == '[' || chr == '}' || chr == ']' || chr == ',')) {
lineForwardFlag = true;
} else {
if (lineForwardFlag) {
result.append("\n");
for (int j = 0; j < bCount; j++) {
result.append("\t");
}
}
lineForwardFlag = false;
}
result.append(chr);
escapeFlag = (chr == '\\');
}
return result.toString();
}
/**
* Warning only set the key in case your sure what you're doing - this can
* spoil the integrity of the JSON-tree - e.g. the key might be used by
* super-objects to reference to this object
*
* @param key
*/
public void setKey(String key) {
this.key = key;
}
public Iterator<Entry<String, JSONObject>> iterator() {
if (children == null) { //lazy instantiation
children = new LinkedHashMap();
}
return children.entrySet().iterator();
}
}