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

com.couchbase.lite.Dictionary Maven / Gradle / Ivy

//
// Copyright (c) 2020, 2017 Couchbase, Inc All rights reserved.
//
// 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.couchbase.lite;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.couchbase.lite.internal.DbContext;
import com.couchbase.lite.internal.fleece.FLEncodable;
import com.couchbase.lite.internal.fleece.FLEncoder;
import com.couchbase.lite.internal.fleece.MCollection;
import com.couchbase.lite.internal.fleece.MContext;
import com.couchbase.lite.internal.fleece.MDict;
import com.couchbase.lite.internal.fleece.MValue;
import com.couchbase.lite.internal.utils.DateUtils;
import com.couchbase.lite.internal.utils.Preconditions;


/**
 * Dictionary provides readonly access to dictionary data.
 */
public class Dictionary implements DictionaryInterface, FLEncodable, Iterable {
    //-------------------------------------------------------------------------
    // member variables
    //-------------------------------------------------------------------------
    @NonNull
    protected final Object lock;
    @NonNull
    protected final MDict internalDict;

    //-------------------------------------------------------------------------
    // Constructors
    //-------------------------------------------------------------------------

    Dictionary() {
        internalDict = new MDict();
        lock = getDbLock();
    }

    Dictionary(MValue mv, MCollection parent) {
        internalDict = new MDict();
        internalDict.initInSlot(mv, parent);
        lock = getDbLock();
    }

    Dictionary(MDict mDict, boolean isMutable) {
        internalDict = new MDict();
        internalDict.initAsCopyOf(mDict, isMutable);
        lock = getDbLock();
    }

    //-------------------------------------------------------------------------
    // API - public methods
    //-------------------------------------------------------------------------

    //-------------------------------------------------------------------------
    // Implementation of ReadOnlyDictionaryInterface
    //-------------------------------------------------------------------------

    /**
     * Gets a number of the entries in the dictionary.
     *
     * @return the size of the dictionary
     */
    @Override
    public int count() {
        synchronized (lock) { return (int) internalDict.count(); }
    }

    @NonNull
    @Override
    public List getKeys() {
        synchronized (lock) { return internalDict.getKeys(); }
    }

    /**
     * Gets a property's value as an object. The object types are Blob, Array,
     * Dictionary, Number, or String based on the underlying data type; or nil if the
     * property value is null or the property doesn't exist.
     *
     * @param key the key.
     * @return the object value or null.
     */
    @Nullable
    @Override
    public Object getValue(@NonNull String key) {
        Preconditions.assertNotNull(key, "key");
        synchronized (lock) { return internalDict.get(key).asNative(internalDict); }
    }

    /**
     * Gets a property's value as a String. Returns null if the value doesn't exist, or its value is not a String.
     *
     * @param key the key
     * @return the String or null.
     */
    @Nullable
    @Override
    public String getString(@NonNull String key) {
        Preconditions.assertNotNull(key, "key");
        synchronized (lock) {
            final Object obj = internalDict.get(key).asNative(internalDict);
            return obj instanceof String ? (String) obj : null;
        }
    }

    /**
     * Gets a property's value as a Number. Returns null if the value doesn't exist, or its value is not a Number.
     *
     * @param key the key
     * @return the Number or nil.
     */
    @Nullable
    @Override
    public Number getNumber(@NonNull String key) {
        Preconditions.assertNotNull(key, "key");
        synchronized (lock) { return CBLConverter.asNumber(internalDict.get(key).asNative(internalDict)); }
    }

    /**
     * Gets a property's value as an int.
     * Floating point values will be rounded. The value `true` is returned as 1, `false` as 0.
     * Returns 0 if the value doesn't exist or does not have a numeric value.
     *
     * @param key the key
     * @return the int value.
     */
    @Override
    public int getInt(@NonNull String key) {
        Preconditions.assertNotNull(key, "key");
        synchronized (lock) { return CBLConverter.asInteger(internalDict.get(key), internalDict); }
    }

    /**
     * Gets a property's value as an long.
     * Floating point values will be rounded. The value `true` is returned as 1, `false` as 0.
     * Returns 0 if the value doesn't exist or does not have a numeric value.
     *
     * @param key the key
     * @return the long value.
     */
    @Override
    public long getLong(@NonNull String key) {
        Preconditions.assertNotNull(key, "key");
        synchronized (lock) { return CBLConverter.asLong(internalDict.get(key), internalDict); }
    }

    /**
     * Gets a property's value as an float.
     * Integers will be converted to float. The value `true` is returned as 1.0, `false` as 0.0.
     * Returns 0.0 if the value doesn't exist or does not have a numeric value.
     *
     * @param key the key
     * @return the float value.
     */
    @Override
    public float getFloat(@NonNull String key) {
        Preconditions.assertNotNull(key, "key");
        synchronized (lock) { return CBLConverter.asFloat(internalDict.get(key), internalDict); }
    }

    /**
     * Gets a property's value as an double.
     * Integers will be converted to double. The value `true` is returned as 1.0, `false` as 0.0.
     * Returns 0.0 if the property doesn't exist or does not have a numeric value.
     *
     * @param key the key
     * @return the double value.
     */
    @Override
    public double getDouble(@NonNull String key) {
        Preconditions.assertNotNull(key, "key");
        synchronized (lock) { return CBLConverter.asDouble(internalDict.get(key), internalDict); }
    }

    /**
     * Gets a property's value as a boolean. Returns true if the value exists, and is either `true`
     * or a nonzero number.
     *
     * @param key the key
     * @return the boolean value.
     */
    @Override
    public boolean getBoolean(@NonNull String key) {
        Preconditions.assertNotNull(key, "key");
        synchronized (lock) { return CBLConverter.asBoolean(internalDict.get(key).asNative(internalDict)); }
    }

    /**
     * Gets a property's value as a Blob.
     * Returns null if the value doesn't exist, or its value is not a Blob.
     *
     * @param key the key
     * @return the Blob value or null.
     */
    @Nullable
    @Override
    public Blob getBlob(@NonNull String key) {
        Preconditions.assertNotNull(key, "key");
        synchronized (lock) {
            final Object obj = internalDict.get(key).asNative(internalDict);
            return obj instanceof Blob ? (Blob) obj : null;
        }
    }

    /**
     * Gets a property's value as a Date.
     * JSON does not directly support dates, so the actual property value must be a string, which is
     * then parsed according to the ISO-8601 date format (the default used in JSON.)
     * Returns null if the value doesn't exist, is not a string, or is not parsable as a date.
     * NOTE: This is not a generic date parser! It only recognizes the ISO-8601 format, with or
     * without milliseconds.
     *
     * @param key the key
     * @return the Date value or null.
     */
    @Nullable
    @Override
    public Date getDate(@NonNull String key) {
        Preconditions.assertNotNull(key, "key");
        return DateUtils.fromJson(getString(key));
    }

    /**
     * Get a property's value as a Array, which is a mapping object of an array value.
     * Returns null if the property doesn't exists, or its value is not an array.
     *
     * @param key the key.
     * @return the Array object.
     */
    @Nullable
    @Override
    public Array getArray(@NonNull String key) {
        Preconditions.assertNotNull(key, "key");
        synchronized (lock) {
            final Object obj = internalDict.get(key).asNative(internalDict);
            return obj instanceof Array ? (Array) obj : null;
        }
    }

    /**
     * Get a property's value as a Dictionary, which is a mapping object of an dictionary value.
     * Returns null if the property doesn't exists, or its value is not an dictionary.
     *
     * @param key the key.
     * @return the Dictionary object or null if the key doesn't exist.
     */
    @Nullable
    @Override
    public Dictionary getDictionary(@NonNull String key) {
        Preconditions.assertNotNull(key, "key");
        synchronized (lock) {
            final Object obj = internalDict.get(key).asNative(internalDict);
            return obj instanceof Dictionary ? (Dictionary) obj : null;
        }
    }

    /**
     * Gets content of the current object as an Map. The values contained in the returned
     * Map object are all JSON based values.
     *
     * @return the Map object representing the content of the current object in the JSON format.
     */
    @NonNull
    @Override
    public Map toMap() {
        final Map result = new HashMap<>();
        synchronized (lock) {
            for (String key : internalDict) {
                result.put(key, Fleece.toObject(internalDict.get(key).asNative(internalDict)));
            }
        }
        return result;
    }

    /**
     * Tests whether a property exists or not.
     * This can be less expensive than getValue(String), because it does not have to allocate an Object for the
     * property value.
     *
     * @param key the key
     * @return the boolean value representing whether a property exists or not.
     */
    @Override
    public boolean contains(@NonNull String key) {
        Preconditions.assertNotNull(key, "key");
        synchronized (lock) { return !internalDict.get(key).isEmpty(); }
    }

    /**
     * Return a mutable copy of the dictionary
     *
     * @return the MutableDictionary instance
     */
    @NonNull
    public MutableDictionary toMutable() {
        synchronized (lock) { return new MutableDictionary(internalDict, true); }
    }

    //-------------------------------------------------------------------------
    // Implementation of FLEncodable
    //-------------------------------------------------------------------------

    /**
     * encodeTo(FlEncoder) is internal method. Please don't use this method.
     */
    @Override
    public void encodeTo(FLEncoder enc) { internalDict.encodeTo(enc); }

    //---------------------------------------------
    // Iterable implementation
    //---------------------------------------------

    @NonNull
    @Override
    public Iterator iterator() { return getKeys().iterator(); }

    @Override
    public boolean equals(Object o) {
        if (this == o) { return true; }
        if (!(o instanceof Dictionary)) { return false; }

        final Dictionary m = (Dictionary) o;

        if (m.count() != count()) { return false; }
        for (String key : this) {
            final Object value = getValue(key);
            if (value != null) {
                if (!value.equals(m.getValue(key))) { return false; }
            }
            else {
                if (!(m.getValue(key) == null && m.contains(key))) { return false; }
            }
        }
        return true;
    }

    //---------------------------------------------
    // Override
    //---------------------------------------------

    @Override
    public int hashCode() {
        int h = 0;
        for (String key : this) { h += hashCode(key, getValue(key)); }
        return h;
    }

    @SuppressWarnings("PMD.ConsecutiveLiteralAppends")
    @NonNull
    @Override
    public String toString() {
        final StringBuilder buf = new StringBuilder("Dictionary{(")
            .append((internalDict.isMutable()) ? '+' : '.')
            .append((internalDict.isMutated()) ? '!' : '.')
            .append(')');
        boolean first = true;
        for (String key : getKeys()) {
            if (first) { first = false; }
            else { buf.append(','); }
            buf.append(key).append("=>").append(getValue(key));
        }
        return buf.append('}').toString();
    }

    //---------------------------------------------
    // protected level access
    //---------------------------------------------

    protected boolean isEmpty() { return count() == 0; }

    //---------------------------------------------
    // package level access
    //---------------------------------------------

    MCollection toMCollection() { return internalDict; }

    //---------------------------------------------
    // private
    //---------------------------------------------

    @NonNull
    private Object getDbLock() {
        final MContext context = internalDict.getContext();
        if (context instanceof DbContext) {
            final Database db = ((DbContext) context).getDatabase();
            if (db != null) { return db.getLock(); }
        }
        return new Object();
    }

    // hashCode for pair of key and value
    private int hashCode(String key, Object value) {
        return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy