package sk.stuba.fiit.perconik.data;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nullable;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ForwardingMap;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import sk.stuba.fiit.perconik.data.content.AnyStructuredContent;
import sk.stuba.fiit.perconik.data.content.Content;
import sk.stuba.fiit.perconik.data.type.content.AnyStructuredContentDeserializer;
import sk.stuba.fiit.perconik.utilities.MoreMaps;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterators.concat;
import static com.google.common.collect.Iterators.singletonIterator;
import static com.google.common.collect.Lists.asList;
import static com.google.common.collect.Maps.newLinkedHashMap;
import static sk.stuba.fiit.perconik.data.content.StructuredContents.sequence;
import static sk.stuba.fiit.perconik.utilities.MoreThrowables.initializeCause;
public class AnyStructuredData extends AnyData implements AnyStructuredContent {
public AnyStructuredData() {
super(new Structure());
}
protected AnyStructuredData(final Map<String, Object> other) {
super(Structure.from(other));
}
public static AnyStructuredData fromMap(final Map<String, Object> data) {
return fromMap(AnyStructuredData.class, data);
}
public static AnyStructuredData fromString(final String data) {
return fromString(AnyStructuredData.class, data);
}
public static AnyStructuredData of(final Map<String, Object> other) {
return new AnyStructuredData(other);
}
static final class Structure extends ForwardingMap<String, Object> implements Serializable {
private static final long serialVersionUID = -8851372460060747540L;
final transient Map<String, Object> map;
Structure() {
this.map = newLinkedHashMap();
}
static Structure from(final Map<String, Object> map) {
Structure structure = new Structure();
structure.putAll(map);
return structure;
}
@Override
protected Map<String, Object> delegate() {
return this.map;
}
private static Iterator<String> normalize(final Iterator<String> key) {
checkState(key.hasNext());
String component = key.next();
checkArgument(!component.isEmpty());
Iterator<String> components = sequence(component).iterator();
return key.hasNext() ? concat(components, key) : components;
}
Object put(Iterator<String> key, final Object value) {
key = normalize(key);
String component = key.next();
if (!key.hasNext()) {
return this.map.put(component, value);
}
AnyStructuredData data;
Object other = this.map.get(component);
if (other instanceof AnyStructuredData) {
data = (AnyStructuredData) other;
} else {
data = new AnyStructuredData();
this.map.put(component, data);
}
return data.internal().put(key, value);
}
Object remove(Iterator<String> key) {
key = normalize(key);
String component = key.next();
if (!key.hasNext()) {
return this.map.remove(component);
}
Object value = this.map.get(component);
if (value instanceof AnyStructuredData) {
return ((AnyStructuredData) value).internal().remove(key);
}
return null;
}
boolean contains(Iterator<String> key) {
key = normalize(key);
String component = key.next();
if (!key.hasNext()) {
return this.map.containsKey(component);
}
Object value = this.map.get(component);
if (value instanceof AnyStructuredData) {
return ((AnyStructuredData) value).internal().contains(key);
}
return false;
}
Object get(Iterator<String> key) {
key = normalize(key);
Object value = this.map.get(key.next());
if (!key.hasNext()) {
return value;
}
if (value instanceof AnyStructuredData) {
return ((AnyStructuredData) value).internal().get(key);
}
return null;
}
@Override
public Object put(final String key, final Object value) {
return this.put(singletonIterator(key), value);
}
@Override
public void putAll(final Map<? extends String, ? extends Object> map) {
this.standardPutAll(map);
}
@Override
public Object remove(final Object key) {
return this.remove(singletonIterator((String) key));
}
@Override
public boolean containsKey(final Object key) {
return this.contains(singletonIterator((String) key));
}
@Override
public Object get(final Object key) {
if (key instanceof String) {
return this.get(singletonIterator((String) key));
}
return null;
}
private static final class SerializationProxy<E extends Enum<E>> implements Serializable {
private static final long serialVersionUID = 7166925961646497798L;
private final Map<String, Object> map;
SerializationProxy(final Structure structure) {
this.map = structure.map;
}
private Object readResolve() throws InvalidObjectException {
try {
return from(this.map);
} catch (Exception e) {
throw initializeCause(new InvalidObjectException("Unknown deserialization error"), e);
}
}
}
@SuppressWarnings({"static-method", "unused"})
private void readObject(final ObjectInputStream in) throws InvalidObjectException {
throw new InvalidObjectException("Serialization proxy required");
}
private Object writeReplace() {
return new SerializationProxy<>(this);
}
}
final Structure internal() {
return (Structure) this.other;
}
public AnyData flatten() {
return new AnyData(MoreMaps.flatten(this.toMap(), Joiner.on(separator)));
}
public AnyStructuredData structure() {
return new AnyStructuredData(MoreMaps.structure(this.toMap(), Splitter.on(separator)));
}
public AnyStructuredData merge(final Content content) {
return this.merge(content.toMap());
}
public AnyStructuredData merge(final Map<String, Object> content) {
return this.merge(content.entrySet());
}
public AnyStructuredData merge(final Iterable<? extends Entry<String, Object>> content) {
return this.merge(content.iterator());
}
public AnyStructuredData merge(final Iterator<? extends Entry<String, Object>> content) {
while (content.hasNext()) {
Entry<String, Object> entry = content.next();
this.put(entry.getKey(), entry.getValue());
}
return this;
}
@JsonAnySetter
@JsonDeserialize(using = AnyStructuredContentDeserializer.class)
@Override
public void put(final String key, @Nullable final Object value) {
this.other.put(key, value);
}
public void put(final Iterable<String> key, final Object value) {
this.put(key.iterator(), value);
}
public void put(final Iterator<String> key, final Object value) {
this.internal().put(key, value);
}
@Override
public Object get(final String key) {
return this.other.get(key);
}
public Object get(final String key, final String ... more) {
return this.get(asList(key, more));
}
public Object get(final Iterable<String> key) {
return this.get(key.iterator());
}
public Object get(final Iterator<String> key) {
return this.internal().get(key);
}
}