package com.nominanuda.zen.obj;
import static com.nominanuda.zen.common.Maths.MATHS;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import javax.annotation.Nullable;
import com.nominanuda.zen.common.Check;
import com.nominanuda.zen.common.Ex.NoException;
public class JsonPath {
// private enum MergeFeature {
// arrayAppend,arrayMerge,arraySet,nullLeafAsNoValue,leafPush,leafSet, emptyScalarAsNull
// }
public static final JsonPath JPATH = new JsonPath();
public static final int MERGE_POLICY_OVERRIDE = 0;
public static final int MERGE_POLICY_PUSH = 1;
private static final String MULTIVALUE_SUFFIX = "[]";
private static final int MULTIVALUE_SUFFIX_LEN = MULTIVALUE_SUFFIX.length();
public void copy(Obj src, Obj dst, int policy)
throws UnsupportedOperationException {
switch (policy) {
case MERGE_POLICY_OVERRIDE:
copyOverwite(src, dst);
break;
case MERGE_POLICY_PUSH:
copyPush(src, dst);
break;
default:
throw new UnsupportedOperationException("unknown merge policy:"
+ policy);
}
}
public void copyOverwite(Obj source, Obj target) {
Iterator<Entry<String, Object>> itr = source.iterator();
while (itr.hasNext()) {
Entry<String, Object> etr = itr.next();
String key = etr.getKey();
Object o = etr.getValue();
if (JsonType.isNullablePrimitive(o)) {
target.store(key, o);
} else if (JsonType.isArr(o)) {
copyOverwite((Arr) o, target.newArr(key));
} else if (JsonType.isObj(o)) {
copyOverwite((Obj) o, target.newObj(key));
} else {
throw new IllegalStateException(
o.getClass().getName()
+ " is neither a Stru nor a primitive type or null");
}
}
}
public void copyOverwite(Arr source, Arr target) {
clear(target);
for(Object o : source) {
if (JsonType.isNullablePrimitive(o)) {
target.push(o);
} else if (JsonType.isArr(o)) {
copyOverwite((Arr) o, target.pushArr());
} else if (JsonType.isObj(o)) {
copyOverwite((Obj) o, target.pushObj());
} else {
throw new IllegalStateException(
o.getClass().getName()
+ " is neither a Stru nor a primitive type or null");
}
}
}
private void clear(Arr target) {
int len = target.len();
for(int i = len - 1; i >= 0; i--) {
target.del(i);
}
}
// private void clear(Obj target) {
// target.reset();
// }
/**
* {a:1} {b:1} => {a:1, b:1} {a:1} {a:1} => {a:[1, 1]} {a:1} {a:null} =>
* {a:[1, null]} {a:{b:1}} {a:null} => {a:[{b:1}, null]} {a:{b:1}}
* {a:{b:{c:null}}} => {a:{b:[1,{c:null}]}} {a:1} {a:[2]} => {a:[1,2]}
* {a:[1]} {a:[2]} => {a:[1,2]} {a:{b:1}} {a:[2]} => {a:[{b:1},2]} {a:{b:1}}
* {a:{b:[2]}} => {a:{b:[1,2]}} {a:1} {} => {a:1}}
*/
public void copyPush(Obj source, Obj target) {
Iterator<Entry<String,Object>> itr = source.iterator();
while (itr.hasNext()) {
Entry<String,Object> e = itr.next();
putPush(target, e.getKey(), e.getValue());
}
}
private void putPush(Obj target, String key, Object val) {
if (target.exists(key)) {
Object tval = target.fetch(key);
if (JsonType.isObj(val) && JsonType.isObj(tval)) {
copyPush((Obj) val, (Obj) tval);
} else if (JsonType.isArr(tval)) {
((Arr) tval).push(val);
} else {
Arr a = target.newArr(key);
a.push(tval);
a.push(val);
}
} else {
target.store(key, val);
}
}
public Object getPathSafe(Stru ds, String... pathBits) {
Stru _target = ds;
int len = pathBits.length;
for(int i = 0; i < len; i++) {
String pathBit = pathBits[i];
Object k = toStringOrIntKey(pathBit);
Object newTarget = _get(_target, k);
if(JsonType.isNullablePrimitive(newTarget)) {
return i == len - 1 ? newTarget : null;
} else {
_target = (Stru)newTarget;
}
}
return _target;
}
public Object getPathSafe(Stru ds, String path) {
return getPathSafe(ds, path.split("\\."));
}
public String getPathSafeStr(Stru ds, String... pathBits) {
return (String)getPathSafe(ds, pathBits);
}
public String getPathSafeStr(Stru ds, String path) {
return (String)getPathSafe(ds, path);
}
public Number getPathSafeNum(Stru ds, String... pathBits) {
return (Number)getPathSafe(ds, pathBits);
}
public Number getPathSafeNum(Stru ds, String path) {
return (Number)getPathSafe(ds, path);
}
public Obj getPathSafeObj(Stru ds, String... pathBits) {
return (Obj)getPathSafe(ds, pathBits);
}
public Obj getPathSafeObj(Stru ds, String path) {
return (Obj)getPathSafe(ds, path);
}
public Arr getPathSafeArr(Stru ds, String... pathBits) {
return (Arr)getPathSafe(ds, pathBits);
}
public Arr getPathSafeArr(Stru ds, String path) {
return (Arr)getPathSafe(ds, path);
}
private Object _get(Stru s, Object intOrStringKey) {
if (s.isArr()) {
Arr a = s.asArr();
int k = (int) intOrStringKey;
return k < a.len() ? a.fetch(k) : null;
} else {
return s.asObj().fetch((String)intOrStringKey);
}
}
private boolean _exists(Stru s, Object intOrStringKey) {
if (s.isArr()) {
return s.asArr().exists((int)intOrStringKey);
} else {
return s.asObj().exists((String)intOrStringKey);
}
}
private void _put(Stru s, Object intOrStringKey, Object val) {
if (s.isArr()) {
Arr a = s.asArr();
int k = (int) intOrStringKey;
int d = k - a.len();
if (d < 0) {
a.store(k, val);
} else {
for (int i = 0; i < d; i++) {
a.add(null);
}
a.add(val);
}
} else {
s.asObj().store((String)intOrStringKey, val);
}
}
private Object toStringOrIntKey(String s) {
Check.notNull(s);
return MATHS.isInteger(s) ? Integer.valueOf(s) : s;
}
public void setOrPushProperty(Stru ds, Object key, @Nullable Object value) {
if (_exists(ds, key)) {
Object cur = _get(ds, key);
if (JsonType.isArr(cur)) {
((Arr)cur).push(value);
} else if (ds.isArr() && cur == null) {
_put(ds, key, value);
} else {
Arr darr = Arr.make();
darr.push(cur);
darr.push(value);
_put(ds, key, darr);
}
} else {
_put(ds, key, value);
}
}
public void setPathProperty(Stru ds, String path, @Nullable Object value) {
String[] pathBits = path.split("\\.");
_put(explodePath(ds, path), toStringOrIntKey(pathBits[pathBits.length - 1]), value);
}
public void setOrPushPathProperty(Stru ds, String path, @Nullable Object value) {
String[] pathBits = path.split("\\.");
setOrPushProperty(explodePath(ds, path), toStringOrIntKey(pathBits[pathBits.length - 1]), value);
}
private Stru explodePath(Stru ds, String path) {
Stru _target = ds;
String[] pathBits = path.split("\\.");
int len = pathBits.length;
for (int i = 0; i < len - 1; i++) {
String pathBit = pathBits[i];
Object k = toStringOrIntKey(pathBit);
Object newTarget = _get(_target, k);
if (JsonType.isNullablePrimitive(newTarget)) {
if (MATHS.isInteger(pathBits[i + 1])) {
newTarget = Arr.make();
} else {
newTarget = Obj.make();
}
_put(_target, k, newTarget);
}
_target = (Stru)newTarget;
}
return _target;
}
//if convertor#canConvert returns false value is not added to result
@SuppressWarnings("unchecked")
public <X extends Stru> X convertLeaves(X source, SafeConvertor<Object, Object> convertor) {
return Check.notNull(source) instanceof Obj
? (X)convertLeavesInternal((Obj)source, convertor)
: (X)convertLeavesInternal((Arr)source, convertor);
}
private Obj convertLeavesInternal(Obj source, SafeConvertor<Object, Object> convertor) {
Obj res = Obj.make();
for(Entry<String, Object> e : source) {
String k = e.getKey();
Object v = e.getValue();
if(JsonType.isNullablePrimitive(v) && convertor.canConvert(v)) {
res.store(k, convertor.apply(v));
} else if(v instanceof Obj) {
res.store(k, convertLeavesInternal((Obj)v, convertor));
} else {
res.store(k, convertLeavesInternal((Arr)v, convertor));
}
}
return res;
}
private Arr convertLeavesInternal(Arr source,
SafeConvertor<Object, Object> convertor) {
Arr res = Arr.make();
int len = source.len();
for(int i = 0; i < len; i++) {
Object v = source.fetch(i);
if(JsonType.isNullablePrimitive(v) && convertor.canConvert(v)) {
res.push(convertor.apply(v));
} else if(v instanceof Obj) {
res.push(convertLeavesInternal((Obj)v, convertor));
} else {
res.push(convertLeavesInternal((Arr)v, convertor));
}
}
return res;
}
public interface ObjectConvertor<X, Y, E extends Exception> {
Y apply(X x) throws E;
boolean canConvert(Object o);
}
public interface SafeConvertor<X, Y> extends ObjectConvertor<X, Y, NoException>, Function<X, Y> {
}
public void toFlatMap(Stru from, Map<String, Object> map) {
toFlatMap(map, "", from);
}
private void toFlatMap(Map<String, Object> map, String key, @Nullable Object value) {
switch (JsonType.of(value)) {
case obj:
Obj obj = (Obj) value;
for (String k : obj.keySet()) {
Object val = obj.getStrict(k);
toFlatMap(map, keyJoin(key, k), val);
}
break;
case arr:
Arr arr = ((Arr) value);
int len = arr.len();
for (int i = 0; i < len; i++) {
toFlatMap(map, keyJoin(key, i), arr.get(i));
}
break;
case nil:
case num:
case bool:
case str:
map.put(key, value);
break;
default:
throw new IllegalArgumentException();
}
}
private String keyJoin(String prefix, Object suffix) {
Check.illegalstate.assertTrue(suffix instanceof Integer || suffix instanceof String);
return (prefix.length() > 0) ? prefix + "." + suffix.toString() : suffix.toString();
}
public Stru fromFlatMap(Map<String, Object> map) {
Obj dest = Obj.make();
for (Entry<String, ?> e : map.entrySet()) {
writeScalarOrMultivaluedProperty(dest, e.getKey(), e.getValue());
}
return dest;
}
private void writeScalarOrMultivaluedProperty(Obj target, String path, Object val) {
if (val != null && val.getClass().isArray()) {
val = Arrays.asList((Object[]) val);
}
if (!(val == null || val instanceof Collection<?>)) { // scalar
if (path.endsWith(MULTIVALUE_SUFFIX)) {
path = path.substring(0, path.length() - MULTIVALUE_SUFFIX_LEN) + ".0";
}
writeScalarProperty(target, path, val);
} else { // Collection
Collection<?> l = (Collection<?>) val;
int len = l.size();
if (len == 0) {
return;
// writeScalarProperty(path, null);
} else if (len == 1) {
if (path.endsWith(MULTIVALUE_SUFFIX)) {
path = path.substring(0, path.length() - MULTIVALUE_SUFFIX_LEN) + ".0";
}
writeScalarProperty(target, path, l.iterator().next());
} else {
if (path.endsWith(MULTIVALUE_SUFFIX)) {
path = path.substring(0, path.length() - MULTIVALUE_SUFFIX_LEN);
}
int i = 0;
for (Object v : l) {
writeScalarProperty(target, path + "." + i, v);
i++;
}
}
}
}
private void writeScalarProperty(Obj target, String path, Object val) {
if (val != null && (val.getClass().isArray() || val instanceof Collection<?>)) {
throw new IllegalArgumentException(val.getClass() + " is not a scalar type");
}
// to allow alternative array syntax a[0] instead of a.0
path = path.replaceAll("\\[(\\d+)\\]", "\\.$1");
String[] bits = path.split("\\.");
writeScalarProperty(target, bits, val);
}
private void writeScalarProperty(Obj target, String[] path, Object val) {
String k = path[0];
if (path.length == 1) {
target.put(k, val);
} else {
Object o = target.get(k);
String[] subPath = new String[path.length - 1];// Arrays.copyOfRange(path, 1, path.length);
System.arraycopy(path, 1, subPath, 0, subPath.length);
String nextKey = subPath[0];
if (MATHS.isInteger(nextKey)) {
Arr newTarget = JsonType.isArr(o) ? (Arr) o : target.arr(k);
writeScalarProperty(newTarget, subPath, val);
} else {
Obj newTarget = JsonType.isObj(o) ? (Obj) o : target.obj(k);
writeScalarProperty(newTarget, subPath, val);
}
}
}
private void writeScalarProperty(Arr target, String[] path, Object val) {
Integer k = Integer.parseInt(path[0]);
if (path.length == 1) {
target.store(k, val);
} else {
Object o = target.len() > k ? target.get(k) : null;
// String[] subPath = Arrays.copyOfRange(path, 1, path.length);
String[] subPath = new String[path.length - 1];// Arrays.copyOfRange(path, 1, path.length);
System.arraycopy(path, 1, subPath, 0, subPath.length);
String nextKey = subPath[0];
if (MATHS.isInteger(nextKey)) {
Arr newTarget = JsonType.isArr(o) ? (Arr) o : target.store(k, Arr.make());
writeScalarProperty(newTarget, subPath, val);
} else {
Obj newTarget = JsonType.isObj(o) ? (Obj) o : target.store(k, Obj.make());
writeScalarProperty(newTarget, subPath, val);
}
}
}
}