/*
* ToroDB
* Copyright © 2014 8Kdata Technology (www.8kdata.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.torodb.kvdocument.values;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.UnmodifiableIterator;
import com.google.common.hash.Hashing;
import com.torodb.kvdocument.types.DocumentType;
import com.torodb.kvdocument.values.KvDocument.DocEntry;
import com.torodb.kvdocument.values.heap.InstantKvInstant;
import com.torodb.kvdocument.values.heap.LocalDateKvDate;
import com.torodb.kvdocument.values.heap.LocalTimeKvTime;
import com.torodb.kvdocument.values.heap.MapKvDocument;
import com.torodb.kvdocument.values.heap.StringKvString;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public abstract class KvDocument extends KvValue<KvDocument> implements Iterable<DocEntry<?>> {
private static final long serialVersionUID = 3113643174084420474L;
@Override
public abstract UnmodifiableIterator<DocEntry<?>> iterator();
@Override
public KvDocument getValue() {
return this;
}
@Override
public Class<? extends KvDocument> getValueClass() {
return getClass();
}
@Override
public DocumentType getType() {
return DocumentType.INSTANCE;
}
@Override
public String toString() {
StringBuilder toStringBuilder = new StringBuilder(
Iterables.toString(this));
toStringBuilder.setCharAt(0, '{');
toStringBuilder.setCharAt(toStringBuilder.length() - 1, '}');
return toStringBuilder.toString();
}
/**
*
* @param key
* @return the value associated with that key or null if there is no entry with that key
*/
@Nullable
public KvValue<?> get(String key) {
for (DocEntry<?> entry : this) {
if (entry.getKey().equals(key)) {
return entry.getValue();
}
}
return null;
}
public Iterable<String> getKeys() {
return Iterables.transform(this, new ExtractKeyFunction());
}
public boolean containsKey(String key) {
return get(key) != null;
}
public boolean isEmpty() {
return size() == 0;
}
public int size() {
return Iterables.size(this);
}
/**
* @return the first entry of this document
* @throws NoSuchElementException if it is {@linkplain #isEmpty() empty}
*/
public DocEntry<?> getFirstEntry() throws NoSuchElementException {
if (isEmpty()) {
throw new NoSuchElementException();
}
return iterator().next();
}
/**
* Two documents are equal if they contain the same entries in the same order.
*
* @param obj
* @return
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof KvDocument)) {
return false;
}
KvDocument other = (KvDocument) obj;
return Iterables.elementsEqual(this, other);
}
@Override
public int hashCode() {
return Hashing.goodFastHash(32).hashInt(size()).asInt();
}
@Override
public <R, A> R accept(KvValueVisitor<R, A> visitor, A arg) {
return visitor.visit(this, arg);
}
public abstract static class DocEntry<V> {
public abstract String getKey();
public abstract KvValue<V> getValue();
/**
* Two entries are equals if their keys and values are equal.
*
* @param obj
* @return
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof DocEntry)) {
return false;
}
DocEntry<?> other = (DocEntry<?>) obj;
return this.getKey().equals(other.getKey()) && this.getValue().equals(other.getValue());
}
/**
* The hashCode of a entry is the hash of its key.
*
* @return
*/
@Override
public int hashCode() {
return getKey().hashCode();
}
@Override
public String toString() {
return getKey() + " : " + getValue();
}
}
public static class SimpleDocEntry<V> extends DocEntry<V> {
private final String key;
private final KvValue<V> value;
public SimpleDocEntry(String key, KvValue<V> value) {
this.key = key;
this.value = value;
}
@Override
public String getKey() {
return key;
}
@Override
public KvValue<V> getValue() {
return value;
}
}
public static class Builder {
private final LinkedHashMap<String, KvValue<?>> values = Maps.newLinkedHashMap();
private boolean built = false;
public Builder putValue(String key, KvValue<?> value) {
checkBuilt();
values.put(key, value);
return this;
}
public Builder putValue(String key, boolean value) {
return putValue(key, KvBoolean.from(value));
}
public Builder putValue(String key, Instant value) {
return putValue(key, new InstantKvInstant(value));
}
public Builder putValue(String key, LocalDate value) {
return putValue(key, new LocalDateKvDate(value));
}
public Builder putValue(String key, double value) {
return putValue(key, KvDouble.of(value));
}
public Builder putValue(String key, int value) {
return putValue(key, KvInteger.of(value));
}
public Builder putValue(String key, long value) {
return putValue(key, KvLong.of(value));
}
public Builder putValue(String key, String value) {
return putValue(key, new StringKvString(value));
}
public Builder putValue(String key, LocalTime value) {
return putValue(key, new LocalTimeKvTime(value));
}
public KvDocument build() {
checkBuilt();
built = true;
return new MapKvDocument(values);
}
private void checkBuilt() {
if (built) {
throw new IllegalStateException("This builder has been already used");
}
}
public Builder putNullValue(String key) {
return putValue(key, KvNull.getInstance());
}
}
protected static class FromEntryMap implements
Function<Map.Entry<String, KvValue<?>>, DocEntry<?>> {
public static final FromEntryMap INSTANCE = new FromEntryMap();
private FromEntryMap() {
}
@Override
public DocEntry<?> apply(@Nonnull Map.Entry<String, KvValue<?>> input) {
return new SimpleDocEntry<>(input.getKey(), input.getValue());
}
}
private static class ExtractKeyFunction implements Function<DocEntry<?>, String> {
public ExtractKeyFunction() {
}
@Override
public String apply(@Nonnull DocEntry<?> input) {
return input.getKey();
}
}
}