UserWorkspaceState.java
package com.taxonomy.workspace.service;
import com.taxonomy.dto.ContextHistoryEntry;
import com.taxonomy.dto.ContextMode;
import com.taxonomy.dto.ContextRef;
import java.time.Instant;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.UUID;
import com.taxonomy.versioning.service.ContextNavigationService;
import com.taxonomy.versioning.service.RepositoryStateService;
/**
* Per-user in-memory workspace state.
*
* <p>Holds all the volatile state that was previously shared across all users
* in the singleton {@link ContextNavigationService} and {@link RepositoryStateService}.
* Each user gets their own instance via the {@link WorkspaceManager}.
*
* <p>This includes:
* <ul>
* <li>The current architecture context (branch, commit, mode)</li>
* <li>Navigation history (browser-like back/forward)</li>
* <li>Projection tracking (which commit the DB projection was built from)</li>
* <li>Operation tracking (whether a merge/cherry-pick is in progress)</li>
* </ul>
*
* <p>Thread safety: individual fields are volatile for visibility; compound
* operations should be synchronized externally if needed.
*/
public class UserWorkspaceState {
private final String username;
private final int maxHistory;
// Context navigation state
private volatile ContextRef currentContext;
private final Deque<ContextHistoryEntry> history = new ArrayDeque<>();
// Projection tracking (per-workspace freshness)
private volatile String lastProjectionCommit;
private volatile String lastProjectionBranch;
private volatile Instant lastProjectionTimestamp;
private volatile String lastIndexCommit;
private volatile Instant lastIndexTimestamp;
// Operation tracking (per-workspace)
private volatile String operationKind; // null = no operation
public UserWorkspaceState(String username, int maxHistory) {
this.username = username;
this.maxHistory = maxHistory;
// Initialize with default editable context on draft branch
this.currentContext = new ContextRef(
UUID.randomUUID().toString(),
"draft",
null,
Instant.now(),
ContextMode.EDITABLE,
null, null, null, null, null, false
);
}
// ── Username ────────────────────────────────────────────────────
public String getUsername() {
return username;
}
// ── Context navigation ──────────────────────────────────────────
public ContextRef getCurrentContext() {
return currentContext;
}
public void setCurrentContext(ContextRef ctx) {
this.currentContext = ctx;
}
public List<ContextHistoryEntry> getHistory() {
return new ArrayList<>(history);
}
public void addHistoryEntry(ContextHistoryEntry entry) {
history.addLast(entry);
while (history.size() > maxHistory) {
history.pollFirst();
}
}
public ContextHistoryEntry peekLastHistory() {
return history.peekLast();
}
public ContextHistoryEntry pollLastHistory() {
return history.pollLast();
}
public boolean isHistoryEmpty() {
return history.isEmpty();
}
// ── Projection tracking ─────────────────────────────────────────
public String getLastProjectionCommit() {
return lastProjectionCommit;
}
public String getLastProjectionBranch() {
return lastProjectionBranch;
}
public Instant getLastProjectionTimestamp() {
return lastProjectionTimestamp;
}
public void recordProjection(String commitId, String branch) {
this.lastProjectionCommit = commitId;
this.lastProjectionBranch = branch;
this.lastProjectionTimestamp = Instant.now();
}
public String getLastIndexCommit() {
return lastIndexCommit;
}
public Instant getLastIndexTimestamp() {
return lastIndexTimestamp;
}
public void recordIndexBuild(String commitId) {
this.lastIndexCommit = commitId;
this.lastIndexTimestamp = Instant.now();
}
// ── Operation tracking ──────────────────────────────────────────
public String getOperationKind() {
return operationKind;
}
public boolean isOperationInProgress() {
return operationKind != null;
}
public void beginOperation(String kind) {
this.operationKind = kind;
}
public void endOperation() {
this.operationKind = null;
}
}