StringEncoder.java

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

import com.lucimber.dbus.type.DBusString;
import com.lucimber.dbus.type.DBusUInt32;
import com.lucimber.dbus.type.Type;
import com.lucimber.dbus.util.LoggerUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An encoder which encodes a string to the D-Bus marshalling format using ByteBuffer.
 *
 * @see Encoder
 * @see DBusString
 */
public final class StringEncoder implements Encoder<DBusString, ByteBuffer> {

    private static final Logger LOGGER = LoggerFactory.getLogger(StringEncoder.class);

    private static final int NUL_TERMINATOR_LENGTH = 1;
    private final ByteOrder order;

    /**
     * Constructs a new instance with mandatory parameter.
     *
     * @param order The byte order of the produced bytes.
     */
    public StringEncoder(ByteOrder order) {
        this.order = Objects.requireNonNull(order, "order must not be null");
    }

    @Override
    public EncoderResult<ByteBuffer> encode(DBusString value, int offset) throws EncoderException {
        Objects.requireNonNull(value, "value must not be null");
        try {
            // Calculate padding
            int padding =
                    EncoderUtils.calculateAlignmentPadding(Type.STRING.getAlignment(), offset);

            // Convert string to UTF-8 bytes
            byte[] stringBytes = value.getDelegate().getBytes(StandardCharsets.UTF_8);
            DBusUInt32 length = DBusUInt32.valueOf(stringBytes.length);

            // Encode the length using UInt32Encoder
            Encoder<DBusUInt32, ByteBuffer> lengthEncoder = new UInt32Encoder(order);
            EncoderResult<ByteBuffer> lengthResult = lengthEncoder.encode(length, offset + padding);
            ByteBuffer lengthBuffer = lengthResult.getBuffer();

            // Total buffer size = padding + 4 (length) + stringBytes.length + 1 (NUL)
            int totalSize =
                    padding
                            + lengthResult.getProducedBytes()
                            + stringBytes.length
                            + NUL_TERMINATOR_LENGTH;
            ByteBuffer buffer = ByteBuffer.allocate(totalSize).order(order);

            // Write padding
            for (int i = 0; i < padding; i++) {
                buffer.put((byte) 0);
            }

            // Write length, string, and NUL
            buffer.put(lengthBuffer);
            buffer.put(stringBytes);
            buffer.put((byte) 0); // NUL terminator
            buffer.flip();

            EncoderResult<ByteBuffer> result = new EncoderResultImpl<>(totalSize, buffer);

            LOGGER.debug(
                    LoggerUtils.MARSHALLING,
                    "STRING: {}; Offset: {}; Padding: {}; Produced bytes: {};",
                    value,
                    offset,
                    padding,
                    totalSize);

            return result;
        } catch (Exception ex) {
            throw new EncoderException("Could not encode STRING.", ex);
        }
    }
}