EmbeddedJavaSourceLocator.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.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.sourcelookup.AbstractSourceLookupParticipant;
import org.sandbox.jdt.triggerpattern.internal.EmbeddedJavaCompiler.CompilationResult;
/**
* Source lookup participant that maps debug stack frames from generated
* synthetic classes back to {@code .sandbox-hint} files.
*
* <p>When a breakpoint is hit in a generated {@code HintCode_*} class,
* this participant maps the stack frame back to the original
* {@code .sandbox-hint} file so the debugger shows the correct source.</p>
*
* <p>Compilation results are registered via
* {@link #registerMapping(String, String, CompilationResult)} when
* embedded Java blocks are compiled.</p>
*
* @since 1.5.0
*/
public class EmbeddedJavaSourceLocator extends AbstractSourceLookupParticipant {
private static final Logger LOGGER = Logger.getLogger(EmbeddedJavaSourceLocator.class.getName());
/**
* The prefix for synthetic class names generated from hint files.
*/
static final String SYNTHETIC_PREFIX = "org.sandbox.generated.HintCode_"; //$NON-NLS-1$
/**
* Maps synthetic class names to their hint file paths and compilation results.
*/
private static final Map<String, SourceMapping> MAPPINGS = new ConcurrentHashMap<>();
/**
* A source mapping entry.
*
* @param hintFilePath the workspace-relative path to the {@code .sandbox-hint} file
* @param compilationResult the compilation result with line mappings
*/
public record SourceMapping(String hintFilePath, CompilationResult compilationResult) {
}
/**
* Registers a mapping from a synthetic class to its hint file.
*
* @param syntheticClassName the fully qualified synthetic class name
* @param hintFilePath the workspace-relative path to the hint file
* @param result the compilation result with line mappings
*/
public static void registerMapping(String syntheticClassName, String hintFilePath,
CompilationResult result) {
MAPPINGS.put(syntheticClassName, new SourceMapping(hintFilePath, result));
LOGGER.log(Level.FINE, "Registered source mapping: {0} -> {1}", //$NON-NLS-1$
new Object[] { syntheticClassName, hintFilePath });
}
/**
* Clears all registered mappings.
*/
public static void clearMappings() {
MAPPINGS.clear();
}
/**
* Returns the source mapping for a synthetic class, if any.
*
* @param syntheticClassName the fully qualified synthetic class name
* @return the source mapping, or {@code null} if not found
*/
public static SourceMapping getMapping(String syntheticClassName) {
return MAPPINGS.get(syntheticClassName);
}
/**
* Checks if the given type name is a synthetic class generated from a hint file.
*
* @param typeName the fully qualified type name
* @return {@code true} if the type is a generated hint code class
*/
public static boolean isSyntheticHintClass(String typeName) {
return typeName != null && typeName.startsWith(SYNTHETIC_PREFIX);
}
@Override
public String getSourceName(Object object) {
if (object instanceof IStackFrame frame) {
try {
String typeName = frame.getName();
if (isSyntheticHintClass(typeName)) {
SourceMapping mapping = MAPPINGS.get(typeName);
if (mapping != null) {
return mapping.hintFilePath();
}
}
} catch (Exception e) {
LOGGER.log(Level.FINE, "Failed to get source name from stack frame", e); //$NON-NLS-1$
}
}
return null;
}
}