DBusDict.java

/*
 * SPDX-FileCopyrightText: 2025 Lucimber UG
 * SPDX-License-Identifier: Apache-2.0
 */
package com.lucimber.dbus.type;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * An object that maps keys to values.
 *
 * @param <KeyT> The key's data type.
 * @param <ValueT> The value's data type.
 */
public final class DBusDict<KeyT extends DBusBasicType, ValueT extends DBusType>
        implements Map<KeyT, ValueT>, DBusContainerType {

    private final HashMap<KeyT, ValueT> delegate;
    private final DBusSignature signature;

    /**
     * Constructs a new instance.
     *
     * @param signature a {@link DBusSignature}; must describe a dictionary
     */
    public DBusDict(final DBusSignature signature) {
        this.signature = Objects.requireNonNull(signature);
        if (!signature.isDictionary()) {
            throw new IllegalArgumentException("Signature must describe a dictionary.");
        }
        final DBusSignature subSignature = signature.subContainer().subContainer();
        final List<DBusSignature> singleTypes = subSignature.getChildren();
        if (singleTypes.get(0).isContainerType()) {
            throw new IllegalArgumentException("Key must be a basic D-Bus type.");
        }
        if (singleTypes.get(1).isDictionaryEntry()) {
            throw new IllegalArgumentException("Dict-entry not allowed as value.");
        }
        this.delegate = new HashMap<>();
    }

    /**
     * Constructs a new instance from another.
     *
     * @param other a {@link DBusDict}
     */
    public DBusDict(final DBusDict<KeyT, ValueT> other) {
        delegate = new HashMap<>(other.delegate);
        signature = other.signature;
    }

    // Factory methods for common dictionary types

    /**
     * Creates a dictionary with string keys and string values.
     *
     * @return a new empty DBusDict with string keys and values
     */
    public static DBusDict<DBusString, DBusString> ofStringToString() {
        return new DBusDict<>(DBusSignature.valueOf("a{ss}"));
    }

    /**
     * Creates a dictionary with string keys and variant values.
     *
     * @return a new empty DBusDict with string keys and variant values
     */
    public static DBusDict<DBusString, DBusVariant> ofStringToVariant() {
        return new DBusDict<>(DBusSignature.valueOf("a{sv}"));
    }

    /**
     * Creates a dictionary with string keys and int32 values.
     *
     * @return a new empty DBusDict with string keys and int32 values
     */
    public static DBusDict<DBusString, DBusInt32> ofStringToInt32() {
        return new DBusDict<>(DBusSignature.valueOf("a{si}"));
    }

    /**
     * Creates a dictionary with string keys and int64 values.
     *
     * @return a new empty DBusDict with string keys and int64 values
     */
    public static DBusDict<DBusString, DBusInt64> ofStringToInt64() {
        return new DBusDict<>(DBusSignature.valueOf("a{sx}"));
    }

    /**
     * Creates a dictionary with string keys and boolean values.
     *
     * @return a new empty DBusDict with string keys and boolean values
     */
    public static DBusDict<DBusString, DBusBoolean> ofStringToBoolean() {
        return new DBusDict<>(DBusSignature.valueOf("a{sb}"));
    }

    /**
     * Creates a dictionary with int32 keys and string values.
     *
     * @return a new empty DBusDict with int32 keys and string values
     */
    public static DBusDict<DBusInt32, DBusString> ofInt32ToString() {
        return new DBusDict<>(DBusSignature.valueOf("a{is}"));
    }

    /**
     * Creates a dictionary with object path keys and string values.
     *
     * @return a new empty DBusDict with object path keys and string values
     */
    public static DBusDict<DBusObjectPath, DBusString> ofObjectPathToString() {
        return new DBusDict<>(DBusSignature.valueOf("a{os}"));
    }

    /**
     * Creates a dictionary with string keys and string values from a Java Map.
     *
     * @param map the Java map to convert
     * @return a new DBusDict containing the map entries
     */
    public static DBusDict<DBusString, DBusString> fromStringMap(final Map<String, String> map) {
        final DBusDict<DBusString, DBusString> dict = ofStringToString();
        map.forEach((k, v) -> dict.put(DBusString.valueOf(k), DBusString.valueOf(v)));
        return dict;
    }

    /**
     * Creates a dictionary with string keys and variant values from a Java Map.
     *
     * @param map the Java map to convert
     * @return a new DBusDict containing the map entries as variants
     */
    public static DBusDict<DBusString, DBusVariant> fromVariantMap(final Map<String, Object> map) {
        final DBusDict<DBusString, DBusVariant> dict = ofStringToVariant();
        map.forEach(
                (k, v) -> {
                    if (v instanceof String) {
                        dict.put(
                                DBusString.valueOf(k),
                                DBusVariant.valueOf(DBusString.valueOf((String) v)));
                    } else if (v instanceof Integer) {
                        dict.put(
                                DBusString.valueOf(k),
                                DBusVariant.valueOf(DBusInt32.valueOf((Integer) v)));
                    } else if (v instanceof Long) {
                        dict.put(
                                DBusString.valueOf(k),
                                DBusVariant.valueOf(DBusInt64.valueOf((Long) v)));
                    } else if (v instanceof Boolean) {
                        dict.put(
                                DBusString.valueOf(k),
                                DBusVariant.valueOf(DBusBoolean.valueOf((Boolean) v)));
                    } else if (v instanceof Double) {
                        dict.put(
                                DBusString.valueOf(k),
                                DBusVariant.valueOf(DBusDouble.valueOf((Double) v)));
                    }
                    // Add more type conversions as needed
                });
        return dict;
    }

    @Override
    public DBusSignature getSignature() {
        return signature;
    }

    @Override
    public Type getType() {
        return Type.ARRAY;
    }

    @Override
    public Map<KeyT, ValueT> getDelegate() {
        return new HashMap<>(delegate);
    }

    @Override
    public int size() {
        return delegate.size();
    }

    @Override
    public boolean isEmpty() {
        return delegate.isEmpty();
    }

    @Override
    public boolean containsKey(final Object key) {
        return delegate.containsKey(key);
    }

    @Override
    public boolean containsValue(final Object value) {
        return delegate.containsValue(value);
    }

    @Override
    public ValueT get(final Object key) {
        return delegate.get(key);
    }

    @Override
    public ValueT put(final KeyT key, final ValueT value) {
        return delegate.put(key, value);
    }

    @Override
    public ValueT remove(final Object key) {
        return delegate.remove(key);
    }

    @Override
    public void putAll(final Map<? extends KeyT, ? extends ValueT> m) {
        delegate.putAll(m);
    }

    @Override
    public void clear() {
        delegate.clear();
    }

    @Override
    public Set<KeyT> keySet() {
        return delegate.keySet();
    }

    @Override
    public Collection<ValueT> values() {
        return delegate.values();
    }

    @Override
    public Set<Entry<KeyT, ValueT>> entrySet() {
        return delegate.entrySet();
    }

    /**
     * Returns a set view of the mappings contained in this map.
     *
     * @return a set
     */
    public Set<DBusDictEntry<KeyT, ValueT>> dictionaryEntrySet() {
        final DBusSignature subSig = signature.subContainer();
        return delegate.entrySet().stream()
                .map(e -> new DBusDictEntry<>(subSig, e.getKey(), e.getValue()))
                .collect(Collectors.toSet());
    }

    @Override
    public String toString() {
        return delegate.toString();
    }
}