SandboxHintReconcilingStrategy.java
/*******************************************************************************
* Copyright (c) 2026 Carsten Hammer.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Carsten Hammer - initial API and implementation
*******************************************************************************/
package org.sandbox.jdt.triggerpattern.editor;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.reconciler.DirtyRegion;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.texteditor.ITextEditor;
import org.sandbox.jdt.triggerpattern.api.EmbeddedJavaBlock;
import org.sandbox.jdt.triggerpattern.api.HintFile;
import org.sandbox.jdt.triggerpattern.internal.EmbeddedJavaCompiler;
import org.sandbox.jdt.triggerpattern.internal.EmbeddedJavaCompiler.CompilationResult;
import org.sandbox.jdt.triggerpattern.internal.HintFileParser.HintParseException;
import org.sandbox.jdt.triggerpattern.internal.HintProgramParser;
/**
* Reconciling strategy for {@code .sandbox-hint} files.
*
* <p>Validates complete composed programs using {@link HintProgramParser} and
* creates error markers for parse, predicate, arity and recursion errors. It
* also compiles embedded Java ({@code <? ?>}) blocks via
* {@link EmbeddedJavaCompiler} and maps compilation errors back to the hint
* file line numbers.</p>
*
* @since 1.3.6
*/
public class SandboxHintReconcilingStrategy implements IReconcilingStrategy {
private static final String MARKER_TYPE= "org.eclipse.core.resources.problemmarker"; //$NON-NLS-1$
private static final String EMBEDDED_JAVA_MARKER_TYPE=
"sandbox_common.org.sandbox.jdt.triggerpattern.embeddedJavaProblem"; //$NON-NLS-1$
private IDocument document;
private ISourceViewer sourceViewer;
private SandboxHintEditor editor;
public void setSourceViewer(ISourceViewer viewer) {
this.sourceViewer= viewer;
}
public void setEditor(SandboxHintEditor editor) {
this.editor= editor;
}
@Override
public void setDocument(IDocument document) {
this.document= document;
}
@Override
public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
reconcile(subRegion);
}
@Override
public void reconcile(IRegion partition) {
if (document == null) {
return;
}
IFile file= getFile();
if (file == null || !file.exists()) {
return;
}
try {
file.deleteMarkers(MARKER_TYPE, true, IResource.DEPTH_ZERO);
file.deleteMarkers(EMBEDDED_JAVA_MARKER_TYPE, true, IResource.DEPTH_ZERO);
} catch (CoreException exception) {
logError("Failed to clear markers", exception); //$NON-NLS-1$
}
HintFile hintFile= null;
try {
hintFile= new HintProgramParser().parse(document.get()).hintFile();
} catch (HintParseException exception) {
createErrorMarker(file, exception);
}
if (hintFile != null) {
validateEmbeddedJavaBlocks(file, hintFile);
}
if (editor != null) {
Display display= Display.getDefault();
if (display != null && !display.isDisposed()) {
display.asyncExec(() -> {
editor.updateFolding();
editor.updateOutline();
});
}
}
}
private void validateEmbeddedJavaBlocks(IFile file, HintFile hintFile) {
List<EmbeddedJavaBlock> blocks= hintFile.getEmbeddedJavaBlocks();
String ruleId= hintFile.getId();
for (EmbeddedJavaBlock block : blocks) {
if (block.getSource().isBlank()) {
continue;
}
CompilationResult result= EmbeddedJavaCompiler.compile(block, ruleId);
if (result.hasErrors()) {
createEmbeddedJavaMarkers(file, block, result);
}
}
}
private void createEmbeddedJavaMarkers(IFile file, EmbeddedJavaBlock block,
CompilationResult result) {
for (IProblem problem : result.problems()) {
if (!problem.isError()) {
continue;
}
try {
IMarker marker= file.createMarker(EMBEDDED_JAVA_MARKER_TYPE);
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
marker.setAttribute(IMarker.MESSAGE, problem.getMessage());
int hintLine= problem.getSourceLineNumber() + result.lineOffset();
if (hintLine > 0) {
marker.setAttribute(IMarker.LINE_NUMBER, hintLine);
}
int sourceStart= problem.getSourceStart();
int sourceEnd= problem.getSourceEnd();
if (sourceStart >= 0 && sourceEnd >= 0) {
int delimiterLength= 2;
int hintStart= block.getStartOffset() + delimiterLength
+ sourceStart - result.syntheticHeaderLength();
int hintEnd= block.getStartOffset() + delimiterLength
+ sourceEnd - result.syntheticHeaderLength() + 1;
if (hintStart >= 0 && hintEnd > hintStart) {
marker.setAttribute(IMarker.CHAR_START, hintStart);
marker.setAttribute(IMarker.CHAR_END, hintEnd);
}
}
} catch (CoreException exception) {
logError("Failed to create embedded Java marker", exception); //$NON-NLS-1$
}
}
}
private void createErrorMarker(IFile file, HintParseException exception) {
try {
IMarker marker= file.createMarker(MARKER_TYPE);
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
marker.setAttribute(IMarker.MESSAGE, exception.getMessage());
if (exception.getLineNumber() > 0) {
marker.setAttribute(IMarker.LINE_NUMBER, exception.getLineNumber());
}
} catch (CoreException markerFailure) {
logError("Failed to create marker", markerFailure); //$NON-NLS-1$
}
}
private IFile getFile() {
if (sourceViewer == null) {
return null;
}
Object adapter= sourceViewer.getTextWidget().getData("org.eclipse.ui.texteditor"); //$NON-NLS-1$
if (adapter instanceof ITextEditor textEditor) {
IEditorInput input= textEditor.getEditorInput();
if (input instanceof IFileEditorInput fileInput) {
return fileInput.getFile();
}
}
return null;
}
private void logError(String message, CoreException exception) {
ILog log= Platform.getLog(SandboxHintReconcilingStrategy.class);
log.error(message, exception);
}
}