Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 86 additions & 23 deletions c4-server/src/main/java/ru/beeatlas/c4/model/C4DocumentManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,17 @@ public void newLine() {

private Map<File, String> fileContent = new HashMap<>();

private int nextLeadingSpace = 0;
private int nextLeadingSpace = 0;
private Workspace lastParsedWorkspace = null;

// For content hash caching: skip parsing if file unchanged
private Map<String, Integer> contentHashes = new ConcurrentHashMap<>();
private Map<String, PublishDiagnosticsParams> cachedDiagnostics = new ConcurrentHashMap<>();

// For double buffering: parse new models into a separate map
// so hover/definition see the old valid one during parsing
private Map<String, C4DocumentModel> pendingModels = new ConcurrentHashMap<>();

public Workspace getLastParsedWorkspace() {
return lastParsedWorkspace;
}
Expand Down Expand Up @@ -236,14 +244,30 @@ public void onParsedProperty(String name, String value) {
}
}

// Get model for file during parsing.
// First check pendingModels (current parsing cycle),
// if not found — create new one there. Old models in c4Models remain untouched,
// ensuring hover/definition still use valid data.
private C4DocumentModel getModel(File _file) {
String file = _file.getAbsolutePath();
return c4Models.computeIfAbsent(file, key -> new C4DocumentModel(file, true));
return pendingModels.computeIfAbsent(file, key -> new C4DocumentModel(file, true));
}

// Create new model in pendingModels with file content.
// Model saves at main c4Models cache only after successful parsing
// (via commitPendingModels), ensuring an atomic update.
private C4DocumentModel createModel(File file, String content) {
C4DocumentModel model = new C4DocumentModel(content, file.getAbsolutePath());
return c4Models.compute(file.getAbsolutePath(), (k, v) -> model);
pendingModels.put(file.getAbsolutePath(), model);
return model;
}

// Atomically move all models from pendingModels to the main c4Models cache.
// Called only after parsing succeeds — until then,
// hover/definition see the previous valid version.
private void commitPendingModels() {
c4Models.putAll(pendingModels);
pendingModels.clear();
}

private static File findWorksapce(String currentDirectory, String fileName) {
Expand Down Expand Up @@ -271,12 +295,41 @@ private static void updateModel(C4DocumentModel model, CompletableFuture<ViewSet
}

private PublishDiagnosticsParams calcDiagnosticsForFile(File file, String content) {


String filePath = file.getAbsolutePath();
String currentDirectory = file.getParent();

// Find workspace.dsl early — needed for combined cache key
File worksapceFile = findWorksapce(currentDirectory, "workspace.dsl");
boolean isWorkspace = worksapceFile.getAbsolutePath().equals(file.getAbsolutePath());

// Check content hash — if file unchanged, return cached diagnostics without re-parsing.
// For fragment files, combine hash with workspace.dsl content to detect changes in either file.
logger.info("Check content changes for {}", filePath);
int fileHash = content.hashCode();
int newHash = fileHash;
if (!isWorkspace && worksapceFile.exists()) {
String wsContent = fileContent.get(worksapceFile);
if (wsContent != null) {
newHash = 31 * newHash + wsContent.hashCode();
}
}
Integer oldHash = contentHashes.get(filePath);
if (oldHash != null && oldHash.equals(newHash)) {
PublishDiagnosticsParams cached = cachedDiagnostics.get(filePath);
if (cached != null) {
logger.info("Content unchanged, skipping parse for {}", filePath);
return cached;
}
}
logger.info("Content changed, parsing for {}", filePath);
CompletableFuture<ViewSet> layouts = null;

fileContent.put(file, content);

// Clear staging area before starting a new parsing cycle
pendingModels.clear();

File worksapceJson = findWorksapce(currentDirectory, "workspace.json");
if(worksapceJson.exists() && worksapceJson.canRead()) {
if(jsonWorkspace == null || jsonModified != worksapceJson.lastModified()) {
Expand All @@ -302,9 +355,6 @@ private PublishDiagnosticsParams calcDiagnosticsForFile(File file, String conten
StructurizrDslParser parser = new StructurizrDslParser();
List<Diagnostic> errors = new ArrayList<>();

File worksapceFile = findWorksapce(currentDirectory, "workspace.dsl");
boolean isWorkspace = worksapceFile.getAbsolutePath().equals(file.getAbsolutePath());

if (isWorkspace) {
C4DocumentModel model = createModel(file, content);
try {
Expand All @@ -322,12 +372,15 @@ private PublishDiagnosticsParams calcDiagnosticsForFile(File file, String conten
if(errors.isEmpty()) {
updateModel(model, layouts);
}
} else {
errors.clear();
model.setValid(true);
}
}
return new PublishDiagnosticsParams(file.toURI().toString(), errors);
// Parsing complete — commit content hash and
// move models from staging (pendingModels) to the main c4Models cache
PublishDiagnosticsParams result = new PublishDiagnosticsParams(file.toURI().toString(), errors);
contentHashes.put(filePath, newHash);
cachedDiagnostics.put(filePath, result);
commitPendingModels();
return result;
}

C4DocumentModel model = createModel(file, content);
Expand All @@ -336,7 +389,11 @@ private PublishDiagnosticsParams calcDiagnosticsForFile(File file, String conten
parser.parse(content, file);
} catch (StructurizrDslParserException e) {
logger.info("Got structurizr exception {}", e.getMessage());
if (worksapceFile.exists()) {
if (parser.getWorkspace() != null || !worksapceFile.exists()) {
// File has workspace {} (real error) or no workspace.dsl to fall back to
errors.add(createError(e));
} else {
// File is a fragment (no workspace created) — try parsing via workspace.dsl
logger.info("try to parse workspace {}", worksapceFile.getAbsolutePath());
String content0 = fileContent.get(worksapceFile);
if (content0 == null) {
Expand All @@ -349,6 +406,7 @@ private PublishDiagnosticsParams calcDiagnosticsForFile(File file, String conten
if (content0 != null) {
model = createModel(worksapceFile, content0);
createModel(file, content);
parser = new StructurizrDslParser();
try {
model.clear();
parser.parse(content0, worksapceFile);
Expand All @@ -364,16 +422,19 @@ private PublishDiagnosticsParams calcDiagnosticsForFile(File file, String conten
if(errors.isEmpty()) {
updateModel(model, layouts);
}
} else {
errors.clear();
model.setValid(true);
}
}
return new PublishDiagnosticsParams(worksapceFile.toURI().toString(), errors);
// Parsing workspace.dsl (fallback) complete — commit and move models.
// Cache with combined hash (fragment + workspace) so changes in either file invalidate it
PublishDiagnosticsParams result0 = new PublishDiagnosticsParams(worksapceFile.toURI().toString(), errors);
int combinedHash = 31 * fileHash + content0.hashCode();
contentHashes.put(filePath, combinedHash);
cachedDiagnostics.put(filePath, result0);
commitPendingModels();
return result0;
} else {
errors.add(createError(e));
}
} else {
logger.info("ParserException {}", e.getMessage());
errors.add(createError(e));
}
} catch (Exception e) {
logger.error("calcDiagnostics {}", e.getMessage());
Expand All @@ -384,12 +445,14 @@ private PublishDiagnosticsParams calcDiagnosticsForFile(File file, String conten
if(errors.isEmpty()) {
updateModel(model, layouts);
}
} else {
errors.clear();
model.setValid(true);
}
}
return new PublishDiagnosticsParams(file.toURI().toString(), errors);
// Parsing regular .dsl file complete — commit and move models
PublishDiagnosticsParams result = new PublishDiagnosticsParams(file.toURI().toString(), errors);
contentHashes.put(filePath, newHash);
cachedDiagnostics.put(filePath, result);
commitPendingModels();
return result;
}

public List<PublishDiagnosticsParams> calcDiagnostics(File file, String content) {
Expand Down
5 changes: 5 additions & 0 deletions extension/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change Log

## 1.0.19

- Added dsl-content caching: skip parsing if file unchanged
- Fixed error output for include & extended DSL

## 1.0.17

- Fixed bug for .md files
Expand Down
Loading
Loading