WorkflowUndoPreview.java
package org.hammer.audio.workflow.collaboration;
import java.io.Serial;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
/**
* Immutable server-computed undo preview.
*
* @param previewId identity bound to session revision and selected target
* @param targetOperationId selected operation
* @param targetActorId original operation actor
* @param operationType semantic operation type
* @param targetOccurredAt target operation occurrence timestamp
* @param affectedObjectIds semantic objects affected by the target
* @param revision revision at which the preview is valid
* @param blockingOperations later conflicting operations, empty when undo is safe
*/
public record WorkflowUndoPreview(
String previewId,
String targetOperationId,
String targetActorId,
String operationType,
Instant targetOccurredAt,
List<String> affectedObjectIds,
long revision,
List<BlockingOperation> blockingOperations) {
public WorkflowUndoPreview {
previewId = requireNotBlank(previewId, "previewId");
targetOperationId = requireNotBlank(targetOperationId, "targetOperationId");
targetActorId = requireNotBlank(targetActorId, "targetActorId");
operationType = requireNotBlank(operationType, "operationType");
Objects.requireNonNull(targetOccurredAt, "targetOccurredAt");
affectedObjectIds = List.copyOf(Objects.requireNonNull(affectedObjectIds, "affectedObjectIds"));
if (revision < 0) {
throw new IllegalArgumentException("revision must be >= 0");
}
blockingOperations =
List.copyOf(Objects.requireNonNull(blockingOperations, "blockingOperations"));
}
/** Returns whether no later operation blocks the inverse. */
public boolean safe() {
return blockingOperations.isEmpty();
}
/**
* Later operation that intersects the selected target's semantic objects.
*
* @param operationId stable id of the later blocking operation
* @param actorId actor that authored the blocking operation
* @param conflictingObjectIds semantic object ids shared with the target operation
*/
public record BlockingOperation(
String operationId, String actorId, List<String> conflictingObjectIds)
implements Serializable {
@Serial private static final long serialVersionUID = 1L;
public BlockingOperation {
operationId = requireNotBlank(operationId, "operationId");
actorId = requireNotBlank(actorId, "actorId");
conflictingObjectIds =
List.copyOf(Objects.requireNonNull(conflictingObjectIds, "conflictingObjectIds"));
if (conflictingObjectIds.isEmpty()) {
throw new IllegalArgumentException("conflictingObjectIds must not be empty");
}
}
}
private static String requireNotBlank(String value, String name) {
Objects.requireNonNull(value, name);
if (value.isBlank()) {
throw new IllegalArgumentException(name + " must not be blank");
}
return value;
}
}