/*
* Copyright 2008-2016 the original author or authors.
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
package com.nominanuda.zen.obj;
import static com.nominanuda.zen.common.Check.illegalargument;
import static com.nominanuda.zen.obj.JsonDeserializer.JSON_DESERIALIZER;
import java.io.InputStream;
import java.io.Reader;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nullable;
import com.nominanuda.zen.common.Check;
import com.nominanuda.zen.stereotype.Value;
public interface Obj extends Stru, Iterable<Entry<String, Object>>, Map<String,Object>/*ReadOnlyCollection<Entry<String, Object>>,*/ {
public static Obj make(Object...keysAndVals) {
illegalargument.assertTrue(keysAndVals.length % 2 == 0, "odd number of arguments");
ObjImpl o = new ObjImpl();
if(keysAndVals != null) {
int halflen = keysAndVals.length / 2;
for(int i = 0; i < halflen; i++) {
o.store((String) keysAndVals[i * 2], keysAndVals[i * 2 + 1]);
}
}
return o;
}
<T> T store(String k, @Nullable T v);
@Nullable
Object fetch(String k);
@Nullable
Object del(String k);
@Override
default int len() {
int i = 0;
for(@SuppressWarnings("unused") Entry<String, Object> e : this) {
i++;
}
return i;
}
@Override
default Obj reset() {
for(Entry<String, Object> e : this) {
del(e.getKey());
}
return this;
}
@Override
default Obj copy() {
return this;
}
@Override
default void sendTo(JixHandler sink) {
sink.startObj();
for(Entry<String, Object> e : this) {
sink.key(Key.of(e.getKey()));
Any.toStruObjModel(e.getValue()).sendTo(sink);
}
sink.endObj();
}
@SuppressWarnings("unchecked")
default <T extends Any> T storeAny(Key k, Any val) {
return (T)Any.toStruObjModel(store(k.get(), val.toJavaObjModel()));
}
default Any fetchAny(Key k) {
return Any.toStruObjModel(fetch(k.get()));
}
default boolean exists(Object k) {
return containsKey(k);
}
/**
* set the value if unset ({@link #indexOfKey(String)} == -1 ) or creates an {@link Arr}
* and pushes the value to it; if an {@link Arr} is already present simply
* pushes the new value to it; if the pushed value is an array itself every member is pushed
* <pre>
* {}.push(x, null) -> {x:null}
* {}.push(x, 1) -> {x:1}
* {}.push(x, {}) -> {x:{}}
* {}.push(x, []) -> {}
* {}.push(x, [[]]) -> {x:[]}
* {}.push(x, [[1]]) -> {x:[1]}
* {}.push(x, [1]) -> {x:1}
* {x:{}}.push(x, null) -> {x:[{},null]}
* {x:{y:1}}.push(x, 1) -> {x:[1,{y:1}]}
* {x:{y:1}}.push(x, {y:2}) -> {x: [{y:2},{y:1}]}
* {x:{y:1}}.push(x, []) -> {x:{y:1}}
* {x:{y:1}}.push(x, [1,[]])-> {x: [{y:1},1,[]]}
* </pre>
* @param k
* @param v
* @return the cardinality
* @throws IllegalArgumentException
*/
// int push(String k, @Nullable Object v) throws IllegalArgumentException;
default int push(String k, Object v) {
return pushImpl(this, k, v, false);
}
//not to be called from ouside
default int pushImpl(Obj o, String k, Object v, boolean recursive) {
if(!recursive && v != null && v instanceof Arr) {
Arr arr = (Arr)v;
if(arr.len() == 0) {
Key key = Key.of(k);
Any a = o.fetchAny(key);
if(a == null) {
return 0;
} else if(a.isArr()) {
return a.asArr().len();
} else {
return 1;
}
}
int res = 0;
for(Object x : (Arr)v) {
res = pushImpl(o, k, x, true);
}
return res;
} else {
Key key = Key.of(k);
Any newVal = Any.toStruObjModel(v);
//illegalargument.assertTrue(newVal.isVal(), "only primitive types can be pushed");
Any a = o.fetchAny(key);
if(a == null) {
o.storeAny(key, newVal);
return 1;
} else if(a.isArr() && a.asArr().len() > 0) {
Arr arr = a.asArr();
arr.push(v);
return arr.len();
} else {
Arr arr = newArr();
arr.push(a);
arr.push(v);
o.storeAny(key, arr);
return 2;
}
}
}
@Override
default JsonType getType() {
return JsonType.obj;
}
///////////Collection
/// other accessors
default Obj obj(String k) throws ClassCastException {
return (Obj)fetch(k);
}
default Arr arr(String k) throws ClassCastException {
return (Arr)fetch(k);
}
default String str(String k) throws ClassCastException {
return (String)fetch(k);
}
default Obj with(String k, Object v) {
store(k, v);
return this;
}
default Arr newArr(String key) {
Arr a = newArr();
store(key, a);
return a;
}
default Obj newObj(String key) {
Obj o = newObj();
store(key, o);
return o;
}
//TODO default Integer getInt(String k) throws ClassCastException {
// return ((Long)fetch(k)).;
// }
////Map
@Override
default boolean containsKey(Object key) {
if (key instanceof String) {
for (Entry<String, Object> x : this) {
if (key.equals(x.getKey())) {
return true;
}
}
}
return false;
}
@Override
default boolean containsValue(Object value) {
for (Entry<String, Object> x : this) {
if (Value.nullSafeEquals(value, x.getValue())) {
return true;
}
}
return false;
}
@Override
default int size() {
return len();
}
@Override
default boolean isEmpty() {
return len() == 0;
}
@Override
default Object get(Object key) {
return key instanceof String ? fetch((String)key) : null;
}
@Override
default Object put(String key, Object value) {
Object ret = fetch(key);
store(key, value);
return ret;
}
@Override
default Object remove(Object key) {
return key instanceof String ? del((String)key) : null;
}
@Override
default void clear() {
reset();
}
@Override
default void putAll(Map<? extends String, ? extends Object> m) {
for(Entry<? extends String, ? extends Object> e : m.entrySet()) {
store(e.getKey(), e.getValue());
}
}
@Override
default Set<String> keySet() {
LinkedHashSet<String> s = new LinkedHashSet<>();
this.forEach((e) -> s.add(e.getKey()));
return s;
}
@Override
default Collection<Object> values() {
LinkedList<Object> l = new LinkedList<>();
this.forEach((e) -> l.add(e.getValue()));
return l;
}
@Override
default Set<Entry<String, Object>> entrySet() {
LinkedHashSet<Entry<String, Object>> s = new LinkedHashSet<>();
this.forEach((e) -> s.add(e));
return s;
}
//Convenience Methods
default String getStr(String k) {
return (String)fetch(k);
}
default String getStrictStr(String key) {
return (String)getStrict(key);
}
default Obj getObj(String k) {
return (Obj)fetch(k);
}
default Obj getStrictObj(String k) {
return (Obj)getStrict(k);
}
default Arr getArr(String k) {
return (Arr)fetch(k);
}
default Arr getStrictArr(String k) {
return (Arr)getStrict(k);
}
default Number getNum(String k) {
return (Number)fetch(k);
}
default Number getStrictNum(String k) {
return (Number)getStrict(k);
}
default Double getDouble(String k) {
Number n = getNum(k);
return n != null ? n.doubleValue() : null;
}
default Double getStrictDouble(String k) {
Number n = getStrictNum(k);
return n != null ? n.doubleValue() : null;
}
default Integer getInt(String k) {
Number n = getNum(k);
return n != null ? n.intValue() : null;
}
default Integer getStrictInt(String k) {
Number n = getStrictNum(k);
return n != null ? n.intValue() : null;
}
default Long getLong(String k) {
Number n = getNum(k);
return n != null ? n.longValue() : null;
}
default Long getStrictLong(String k) {
Number n = getStrictNum(k);
return n != null ? n.longValue() : null;
}
default Boolean getBoolean(String k) {
return (Boolean)fetch(k);
}
default boolean getStrictBoolean(String k) {
return Boolean.valueOf(getBoolean(k));
}
public static Obj parse(InputStream is) {
return (Obj)JSON_DESERIALIZER.deserialize(is);
}
public static Obj parse(Reader r) {
return (Obj)JSON_DESERIALIZER.deserialize(r);
}
public static Obj parse(String r) {
return (Obj)JSON_DESERIALIZER.deserialize(r);
}
default Object getStrict(String k) {
return Check.notNull(fetch(k));
}
default Obj putObj(String key) {
Obj o = newObj();
store(key, o);
return o;
}
default Arr putArr(String key) {
Arr arr = newArr();
store(key, arr);
return arr;
}
static Obj fromMap(Map<?, ?> m) {
Obj o = make();
StruUtils.deepCopy(m, o);
return o;
}
@SuppressWarnings("unchecked")
default <K> Map<String,K> of(Class<K> klass) {
return (Map<String,K>)this;
}
default Map<String,Obj> ofObj() {
return of(Obj.class);
}
public static Map<String,Obj> ofObj(@Nullable Obj o) {
return (o != null ? o : make()).ofObj();
}
default Map<String,Arr> ofArr() {
return of(Arr.class);
}
public static Map<String,Arr> ofArr(@Nullable Obj o) {
return (o != null ? o : make()).ofArr();
}
default Map<String,String> ofStr() {
return of(String.class);
}
public static Map<String,String> ofStr(@Nullable Obj o) {
return (o != null ? o : make()).ofStr();
}
}