OutboundMethodCall.java
/*
* SPDX-FileCopyrightText: 2023-2025 Lucimber UG
* SPDX-License-Identifier: Apache-2.0
*/
package com.lucimber.dbus.message;
import com.lucimber.dbus.type.DBusObjectPath;
import com.lucimber.dbus.type.DBusSignature;
import com.lucimber.dbus.type.DBusString;
import com.lucimber.dbus.type.DBusType;
import com.lucimber.dbus.type.DBusUInt32;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
/**
* An outbound method call.
*
* @since 1.0
*/
public final class OutboundMethodCall extends AbstractMethodCall implements OutboundMessage {
private final DBusString dst;
private final boolean replyExpected;
private final Duration timeout;
/**
* Constructs a new instance with all parameter. Please use the builder instead.
*
* @param serial the serial number
* @param path the object path
* @param member the name of the method
* @param replyExpected states if reply is expected
* @param dst optional; the destination of this method call
* @param iface optional; the name of the interface
* @param signature optional; the signature of the message body
* @param payload optional; the message body
*/
public OutboundMethodCall(
DBusUInt32 serial,
DBusObjectPath path,
DBusString member,
boolean replyExpected,
DBusString dst,
DBusString iface,
DBusSignature signature,
List<? extends DBusType> payload) {
this(serial, path, member, replyExpected, dst, iface, signature, payload, null);
}
/**
* Constructs a new instance with all parameter including timeout. Please use the builder
* instead.
*
* @param serial the serial number
* @param path the object path
* @param member the name of the method
* @param replyExpected states if reply is expected
* @param dst optional; the destination of this method call
* @param iface optional; the name of the interface
* @param signature optional; the signature of the message body
* @param payload optional; the message body
* @param timeout optional; timeout override for this specific call
*/
public OutboundMethodCall(
DBusUInt32 serial,
DBusObjectPath path,
DBusString member,
boolean replyExpected,
DBusString dst,
DBusString iface,
DBusSignature signature,
List<? extends DBusType> payload,
Duration timeout) {
super(serial, path, member, iface, signature, payload);
this.dst = dst;
this.replyExpected = replyExpected;
this.timeout = timeout;
}
@Override
public Optional<DBusString> getDestination() {
return Optional.ofNullable(dst);
}
/**
* States if the sender expects a reply to this method call or not.
*
* @return {@code TRUE} if reply is expected, {@code FALSE} otherwise.
*/
public boolean isReplyExpected() {
return replyExpected;
}
/**
* Gets the timeout override for this method call.
*
* @return the timeout duration, or empty if no override is specified
*/
public Optional<Duration> getTimeout() {
return Optional.ofNullable(timeout);
}
@Override
public String toString() {
var s =
"OutboundMethodCall{dst='%s', serial='%s', path='%s', iface='%s', member='%s', sig='%s'}";
var dst = getDestination().map(DBusString::toString).orElse("");
var iface = getInterfaceName().map(DBusString::toString).orElse("");
var sig = getSignature().map(DBusSignature::toString).orElse("");
return String.format(s, dst, getSerial(), getObjectPath(), iface, getMember(), sig);
}
public static class Builder {
private DBusUInt32 serial;
private DBusObjectPath path;
private DBusString destination;
private DBusString iface;
private DBusString member;
private boolean replyExpected;
private DBusSignature signature;
private List<? extends DBusType> payload;
private Duration timeout;
private Builder() {
replyExpected = false;
}
public static Builder create() {
return new Builder();
}
public Builder withSerial(DBusUInt32 serial) {
this.serial = serial;
return this;
}
public Builder withPath(DBusObjectPath path) {
this.path = path;
return this;
}
public Builder withMember(DBusString member) {
this.member = member;
return this;
}
public Builder withReplyExpected(boolean replyExpected) {
this.replyExpected = replyExpected;
return this;
}
public Builder withDestination(DBusString destination) {
this.destination = destination;
return this;
}
public Builder withInterface(DBusString iface) {
this.iface = iface;
return this;
}
public Builder withBody(DBusSignature signature, List<? extends DBusType> payload) {
this.signature = signature;
this.payload = payload;
return this;
}
/**
* Sets a timeout override for this specific method call.
*
* @param timeout The timeout duration (must be positive)
* @return This builder instance
* @throws IllegalArgumentException if timeout is null or not positive
*/
public Builder withTimeout(Duration timeout) {
if (timeout != null && (timeout.isNegative() || timeout.isZero())) {
throw new IllegalArgumentException("Timeout must be positive");
}
this.timeout = timeout;
return this;
}
public OutboundMethodCall build() {
validate();
return new OutboundMethodCall(
serial,
path,
member,
replyExpected,
destination,
iface,
signature,
payload,
timeout);
}
private void validate() {
if (serial == null) {
throw new InvalidMessageException("Serial number must not be null.");
}
if (path == null) {
throw new InvalidMessageException("Object path must not be null.");
}
if (member == null) {
throw new InvalidMessageException("Member name must not be null.");
} else if (member.getDelegate().isBlank()) {
throw new InvalidMessageException("Member name must be set and not blank.");
}
if (destination != null && destination.getDelegate().isBlank()) {
throw new InvalidMessageException("Destination must not be blank.");
}
if (iface != null && iface.getDelegate().isBlank()) {
throw new InvalidMessageException("Interface name must not be blank.");
}
if (signature == null && payload != null) {
throw new InvalidMessageException(
"Payload is present, but signature is missing "
+ "– both must be set together or left null.");
} else if (signature != null && payload == null) {
throw new InvalidMessageException(
"Signature is present, but payload is missing "
+ "– both must be set together or left null.");
}
}
}
}