DictEncoder.java
/*
* SPDX-FileCopyrightText: 2023-2025 Lucimber UG
* SPDX-License-Identifier: Apache-2.0
*/
package com.lucimber.dbus.codec.encoder;
import com.lucimber.dbus.type.DBusBasicType;
import com.lucimber.dbus.type.DBusDict;
import com.lucimber.dbus.type.DBusDictEntry;
import com.lucimber.dbus.type.DBusSignature;
import com.lucimber.dbus.type.DBusType;
import com.lucimber.dbus.type.DBusUInt32;
import com.lucimber.dbus.type.Type;
import com.lucimber.dbus.util.LoggerUtils;
import java.lang.invoke.MethodHandles;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An encoder which encodes a dictionary to the D-Bus marshalling format using ByteBuffer.
*
* @param <KeyT> The data type of the key.
* @param <ValueT> The data type of the value.
* @see Encoder
* @see DBusDict
*/
public final class DictEncoder<KeyT extends DBusBasicType, ValueT extends DBusType>
implements Encoder<DBusDict<KeyT, ValueT>, ByteBuffer> {
private static final Logger LOGGER =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final ByteOrder order;
private final DBusSignature signature;
/**
* Constructs a new instance.
*
* @param order The byte order of the produced bytes.
* @param signature The full signature of the dictionary.
*/
public DictEncoder(ByteOrder order, DBusSignature signature) {
this.order = Objects.requireNonNull(order, "order must not be null");
this.signature = Objects.requireNonNull(signature, "signature must not be null");
}
@Override
public EncoderResult<ByteBuffer> encode(DBusDict<KeyT, ValueT> dict, int offset)
throws EncoderException {
Objects.requireNonNull(dict, "dict must not be null");
try {
int padding = EncoderUtils.calculateAlignmentPadding(Type.ARRAY.getAlignment(), offset);
// Prepare encoder for dictionary entries
DictEntryEncoder<KeyT, ValueT> entryEncoder =
new DictEntryEncoder<>(order, signature.subContainer());
// Encode all entries
ByteBuffer[] encodedEntries = new ByteBuffer[dict.dictionaryEntrySet().size()];
int entryIndex = 0;
int entryOffsetBase = offset + padding + 4; // 4 bytes reserved for array length
int typePadding =
EncoderUtils.calculateAlignmentPadding(
Type.DICT_ENTRY.getAlignment(), entryOffsetBase);
int entryBytesTotal = 0;
for (DBusDictEntry<KeyT, ValueT> entry : dict.dictionaryEntrySet()) {
int entryOffset = entryOffsetBase + typePadding + entryBytesTotal;
EncoderResult<ByteBuffer> encoded = entryEncoder.encode(entry, entryOffset);
encodedEntries[entryIndex] = encoded.getBuffer();
entryBytesTotal += encoded.getProducedBytes();
entryIndex++;
}
// Encode the size field
int fullArrayLength = entryBytesTotal;
Encoder<DBusUInt32, ByteBuffer> lengthEncoder = new UInt32Encoder(order);
EncoderResult<ByteBuffer> lengthResult =
lengthEncoder.encode(DBusUInt32.valueOf(fullArrayLength), offset + padding);
ByteBuffer lengthBuffer = lengthResult.getBuffer();
// Compose final buffer
int totalSize =
padding + lengthResult.getProducedBytes() + typePadding + entryBytesTotal;
ByteBuffer buffer = ByteBuffer.allocate(totalSize).order(order);
// Write padding
for (int i = 0; i < padding; i++) {
buffer.put((byte) 0);
}
// Write size field
buffer.put(lengthBuffer);
// Write type padding
for (int i = 0; i < typePadding; i++) {
buffer.put((byte) 0);
}
// Write all entries
for (ByteBuffer entryBuf : encodedEntries) {
buffer.put(entryBuf);
}
buffer.flip();
LOGGER.debug(
LoggerUtils.MARSHALLING,
"DICT: {}; Offset: {}; Padding: {}; Produced bytes: {};",
signature,
offset,
padding,
totalSize);
return new EncoderResultImpl<>(totalSize, buffer);
} catch (Exception ex) {
throw new EncoderException("Could not encode DICT.", ex);
}
}
}