All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cedarsoftware.io.JsonObject Maven / Gradle / Ivy

There is a newer version: 4.30.0
Show newest version
package com.cedarsoftware.io;

import java.lang.reflect.Array;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * This class holds a JSON object in a LinkedHashMap.
 * LinkedHashMap used to keep fields in same order as they are
 * when reflecting them in Java.  Instances of this class hold a
 * Map-of-Map representation of a Java object, read from the JSON
 * input stream.
 *
 * @author John DeRegnaucourt ([email protected])
 *         
* Copyright (c) Cedar Software LLC *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* License *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class JsonObject extends JsonValue implements Map { private final Map jsonStore = new LinkedHashMap<>(); private boolean isMap = false; private Integer hash = null; public String toString() { String jType = javaType == null ? "not set" : javaType.getName(); String targetInfo = target == null ? "null" : jType; return "JsonObject(id:" + id + ", type:" + jType + ", target:" + targetInfo + ", line:" + line + ", col:" + col + ", size:" + size() + ")"; } public Object setFinishedTarget(Object o, boolean isFinished) { setTarget(o); this.isFinished = isFinished; return target; } // Map APIs public boolean isMap() { return isMap || target instanceof Map; } // Collection APIs public boolean isCollection() { if (target instanceof Collection) { return true; } if (containsKey(ITEMS) && !containsKey(KEYS)) { Class type = javaType; return type != null && !type.isArray(); } return false; } // Array APIs public boolean isArray() { if (target == null) { if (javaType != null) { return javaType.isArray(); } return containsKey(ITEMS) && !containsKey(KEYS); } return target.getClass().isArray(); } // Return the array that this JSON object wraps. This is used when there is a Collection class (like ArrayList) // represented in the JSON. This also occurs if a specified array type is used (not Object[], but Integer[], for // example). public Object[] getJsonArray() { return (Object[]) get(ITEMS); } public void setJsonArray(Object[] jsonArray) { put(ITEMS, jsonArray); } public int getLength() { Integer items = getLenientSize(); if (items != null) { return items; } throw new JsonIoException("getLength() called on a non-collection, line " + line + ", col " + col); } private Integer getLenientSize() { if (isArray()) { if (target == null) { Object[] items = getJsonArray(); return items == null ? 0 : items.length; } if (char[].class.isAssignableFrom(target.getClass())) { // Verify this for Character[] return 1; } return Array.getLength(target); } if (isCollection() || isMap()) { Object[] items = getJsonArray(); return items == null ? 0 : items.length; } return null; } public void setValue(Object o) { put(VALUE, o); } public Object getValue() { return get(VALUE); } public boolean hasValue() { return containsKey(VALUE) && size() == 1; } public int size() { if (containsKey(ITEMS)) { if (getJsonArray() == null) { return 0; } return getJsonArray().length; } return jsonStore.size(); } private int hashCode(Object array, Map seen) { if (array == null) { return super.hashCode(); } if (!array.getClass().isArray()) { return super.hashCode(); } if (seen.containsKey(array)) { return hash; } seen.put(array, null); int result = 1; int length = Array.getLength(array); for (int i = 0; i < length; i++) { Object item = Array.get(array, i); hash = hashCode(item, seen); result = 31 * result + hash; } seen.remove(array); return result; } public int hashCode() { if (hash == null) { if (isArray() || isCollection()) { hash = hashCode(getJsonArray(), new IdentityHashMap<>()); } else { hash = jsonStore.hashCode(); } } return hash; } public boolean isEmpty() { return jsonStore.isEmpty(); } public boolean containsKey(Object key) { return jsonStore.containsKey(key); } public boolean containsValue(Object value) { return jsonStore.containsValue(value); } public Object get(Object key) { return jsonStore.get(key); } public Object remove(Object key) { hash = null; return jsonStore.remove(key); } // TODO: What value is flipping isMap that our isMap() API is not catching? public Object put(Object key, Object value) { hash = null; if ((ITEMS.equals(key) && containsKey(KEYS)) || (KEYS.equals(key) && containsKey(ITEMS))) { isMap = true; } return jsonStore.put(key, value); } public void putAll(Map map) { hash = null; jsonStore.putAll(map); } public void clear() { super.clear(); jsonStore.clear(); hash = null; } public Set keySet() { return jsonStore.keySet(); } public Collection values() { return jsonStore.values(); } public Set> entrySet() { return jsonStore.entrySet(); } /** * Return the keys/values of this Map as a Map.Entry, where the key is Object[] of keys, and the value is * Object[] values. Currently, this has a side effect on the JsonObject, changing how it stores the keys * and items internally. No calling code should be written to be sensitive to this. */ Map.Entry asTwoArrays() { if (!containsKey(KEYS) && !isReference()) { final Object[] keys = new Object[size()]; final Object[] values = new Object[size()]; int i = 0; for (Object e : entrySet()) { final Map.Entry entry = (Map.Entry) e; keys[i] = entry.getKey(); values[i] = entry.getValue(); i++; } put(KEYS, keys); put(ITEMS, values); } return new AbstractMap.SimpleImmutableEntry<>((Object[]) get(KEYS), (Object[]) get(ITEMS)); } void rehashMaps(boolean useMapsLocal, Object[] keys, Object[] items) { Object[] javaKeys, javaValues; Map map; if (useMapsLocal) { // Move from two Object[]'s storage internally back to Map(key, value) map = this; javaKeys = (Object[]) remove(KEYS); javaValues = (Object[]) remove(ITEMS); } else { // Populate peer Java Map instance map = (Map) target; javaKeys = keys; javaValues = items; } jsonStore.clear(); hash = null; int len = javaKeys.length; for (int i=0; i < len; i++) { map.put(javaKeys[i], javaValues[i]); } } }