ContainerFlowIndex.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
*******************************************************************************/
package org.sandbox.jdt.container.analysis;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.sandbox.jdt.internal.common.AstProcessing;
import org.sandbox.jdt.internal.common.ReferenceHolder;
/** Deterministic binding and reference index for one compilation unit. */
final class ContainerFlowIndex {
private final Map<String, BindingInfo> bindings;
private final Map<String, List<SimpleName>> references;
private final Map<String, MethodInfo> methods;
private final String compilationUnitHandle;
private ContainerFlowIndex(
Map<String, BindingInfo> bindings,
Map<String, List<SimpleName>> references,
Map<String, MethodInfo> methods,
String compilationUnitHandle) {
this.bindings= immutableLinkedMap(bindings);
this.references= immutableReferenceMap(references);
this.methods= immutableLinkedMap(methods);
this.compilationUnitHandle= compilationUnitHandle;
}
static ContainerFlowIndex create(CompilationUnit compilationUnit) {
Map<String, BindingInfo> bindings= new LinkedHashMap<>();
Map<String, List<SimpleName>> references= new LinkedHashMap<>();
Map<String, MethodInfo> methods= new LinkedHashMap<>();
Map<String, Integer> parameterIndices= new LinkedHashMap<>();
IJavaElement javaElement= compilationUnit.getJavaElement();
String unitHandle= javaElement == null ? "" : javaElement.getHandleIdentifier(); //$NON-NLS-1$
AstProcessing.independent(ReferenceHolder.<String, Object>create())
.on(MethodDeclaration.class, (method, holder) -> {
indexMethod(method, methods, parameterIndices);
return true;
})
.on(SimpleName.class, (name, holder) -> {
indexVariable(name, bindings, references, parameterIndices, unitHandle);
return true;
})
.build(compilationUnit);
return new ContainerFlowIndex(bindings, references, methods, unitHandle);
}
Map<String, BindingInfo> bindings() {
return bindings;
}
Map<String, List<SimpleName>> references() {
return references;
}
Map<String, MethodInfo> methods() {
return methods;
}
String compilationUnitHandle() {
return compilationUnitHandle;
}
private static void indexMethod(
MethodDeclaration method,
Map<String, MethodInfo> methods,
Map<String, Integer> parameterIndices) {
IMethodBinding binding= method.resolveBinding();
if (binding == null) {
return;
}
List<String> parameterBindingKeys= new ArrayList<>();
for (int index= 0; index < method.parameters().size(); index++) {
SingleVariableDeclaration parameter=
(SingleVariableDeclaration) method.parameters().get(index);
IVariableBinding parameterBinding= parameter.resolveBinding();
String parameterKey= parameterBinding == null
? "" : parameterBinding.getVariableDeclaration().getKey(); //$NON-NLS-1$
parameterBindingKeys.add(parameterKey);
if (parameterKey != null && !parameterKey.isBlank()) {
parameterIndices.put(parameterKey, Integer.valueOf(index));
}
}
methods.put(
binding.getMethodDeclaration().getKey(),
new MethodInfo(parameterBindingKeys));
}
private static void indexVariable(
SimpleName name,
Map<String, BindingInfo> bindings,
Map<String, List<SimpleName>> references,
Map<String, Integer> parameterIndices,
String unitHandle) {
IBinding resolved= name.resolveBinding();
if (!(resolved instanceof IVariableBinding variable)) {
return;
}
IVariableBinding declaration= variable.getVariableDeclaration();
String bindingKey= declaration.getKey();
if (bindingKey == null || bindingKey.isBlank()) {
return;
}
int signatureIndex= parameterIndices.getOrDefault(bindingKey, Integer.valueOf(-1)).intValue();
BindingInfo info= bindings.computeIfAbsent(
bindingKey,
ignored -> new BindingInfo(declaration, unitHandle, signatureIndex));
if (isDeclarationName(name)) {
info.recordDeclaration(name);
}
references.computeIfAbsent(bindingKey, ignored -> new ArrayList<>()).add(name);
}
private static boolean isDeclarationName(SimpleName name) {
return name.getParent() instanceof VariableDeclarationFragment fragment
&& fragment.getName() == name
|| name.getParent() instanceof SingleVariableDeclaration declaration
&& declaration.getName() == name;
}
private static <K, V> Map<K, V> immutableLinkedMap(Map<K, V> source) {
return Collections.unmodifiableMap(new LinkedHashMap<>(source));
}
private static Map<String, List<SimpleName>> immutableReferenceMap(
Map<String, List<SimpleName>> source) {
Map<String, List<SimpleName>> copy= new LinkedHashMap<>();
source.forEach((key, value) -> copy.put(key, List.copyOf(value)));
return Collections.unmodifiableMap(copy);
}
/** Binding metadata used while the parser result is alive. */
static final class BindingInfo {
private final IVariableBinding binding;
private final String compilationUnitHandle;
private final int signatureIndex;
private SimpleName declarationName;
BindingInfo(
IVariableBinding binding,
String compilationUnitHandle,
int signatureIndex) {
this.binding= binding.getVariableDeclaration();
this.compilationUnitHandle= compilationUnitHandle;
this.signatureIndex= signatureIndex;
}
void recordDeclaration(SimpleName name) {
declarationName= name;
}
IVariableBinding binding() {
return binding;
}
SimpleName anchor() {
return declarationName;
}
String compilationUnitHandle() {
return compilationUnitHandle;
}
int signatureIndex() {
return signatureIndex;
}
}
/** Parameter binding keys for one local method declaration. */
static record MethodInfo(List<String> parameterBindingKeys) {
MethodInfo {
parameterBindingKeys= List.copyOf(parameterBindingKeys);
}
}
}