ClosedSourceParameterPlanningIntegrationTest.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 static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
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.SingleVariableDeclaration;
import org.sandbox.jdt.container.api.ContainerBridgePolicyPlan;
import org.sandbox.jdt.container.api.ContainerFlowComponent;
import org.sandbox.jdt.container.api.ContainerFlowContinuationPlan.ContinuationRoot;
import org.sandbox.jdt.container.api.ContainerFlowGraph.ClosureStatus;
import org.sandbox.jdt.container.api.ContainerFlowGraph.FlowNode;
import org.sandbox.jdt.container.api.ContainerFlowGraph.NodeKind;
import org.sandbox.jdt.container.api.ContainerFlowSearchPlan.SearchKind;
import org.sandbox.jdt.container.api.ContainerMigrationReadiness;
import org.sandbox.jdt.container.api.ContainerMigrationReadiness.ExecutionStatus;
import org.sandbox.jdt.container.api.ContainerRecommendation;
import org.sandbox.jdt.container.api.ContainerRecommendation.AutomationLevel;
import org.sandbox.jdt.container.api.ContainerRecommendation.Confidence;
import org.sandbox.jdt.container.api.ContainerRecommendation.ContractAssessment;
import org.sandbox.jdt.container.api.ContainerRecommendation.ContractProperty;
import org.sandbox.jdt.container.api.ContainerRecommendation.Preservation;
import org.sandbox.jdt.container.api.ContainerRuleDescriptor;
import org.sandbox.jdt.container.api.ContainerRuleDescriptor.RuleOwnership;
import org.sandbox.jdt.container.api.ContainerShape;
import org.sandbox.jdt.container.api.ContainerSignatureMigrationPlan;
import org.sandbox.jdt.container.api.ContainerUsageProfile;
import org.sandbox.jdt.container.api.ContainerUsageProfile.NullContract;
import org.sandbox.jdt.container.api.ContainerUsageProfile.OrderRequirement;
import org.sandbox.jdt.container.api.ContainerUsageProfile.UniquenessRequirement;
import org.sandbox.jdt.container.api.ResolvedContainerFlowSearchPlan;
import org.sandbox.jdt.container.api.ResolvedContainerFlowSearchPlan.ResolvedSearchTarget;
import org.sandbox.jdt.container.api.ResolvedContainerFlowSearchPlan.TargetKind;
import org.sandbox.jdt.container.api.TargetContainerContract;
import org.sandbox.jdt.container.api.TargetContainerContract.Mutability;
import org.sandbox.jdt.container.api.UsageEvidence.Kind;
import org.sandbox.jdt.ui.tests.quickfix.rules.AbstractEclipseJava;
import org.sandbox.jdt.ui.tests.quickfix.rules.EclipseJava22;
class ClosedSourceParameterPlanningIntegrationTest {
@RegisterExtension
final AbstractEclipseJava context= new EclipseJava22();
@Test
void continuationSeedReachesTheExecutableParameterPlanner() throws Exception {
ICompilationUnit unit= createUnit("""
package test;
class Sample {
void consume(String[] values) {
System.out.println(values.length);
for (String value : values) {
System.out.println(value);
}
}
}
""");
CompilationUnit root= parse(unit);
MethodDeclaration method= method(root);
SingleVariableDeclaration parameter=
(SingleVariableDeclaration) method.parameters().get(0);
IVariableBinding parameterBinding= parameter.resolveBinding();
IMethodBinding methodBinding= method.resolveBinding();
IJavaElement methodElement= methodBinding == null
? null : methodBinding.getMethodDeclaration().getJavaElement();
if (parameterBinding == null || methodBinding == null || methodElement == null) {
throw new IllegalStateException("Missing resolved method or parameter"); //$NON-NLS-1$
}
String nodeId= "parameter:consume:0"; //$NON-NLS-1$
String parameterKey= parameterBinding.getVariableDeclaration().getKey();
String methodKey= methodBinding.getMethodDeclaration().getKey();
String methodHandle= methodElement.getHandleIdentifier();
ResolvedContainerFlowSearchPlan resolved= new ResolvedContainerFlowSearchPlan(
List.of(new ResolvedSearchTarget(
nodeId,
SearchKind.METHOD_DECLARATION,
TargetKind.METHOD,
parameterKey,
methodKey,
methodHandle,
0,
"Continue from the exact parameter declaration"))); //$NON-NLS-1$
ContinuationRoot continuation= new ContainerFlowContinuationDetector()
.detect(root, unit.getHandleIdentifier(), resolved)
.roots().get(0);
ContainerUsageProfile profile= new LocalArrayUsageAnalyzer()
.analyze(root, continuation.profile());
assertTrue(profile.evidence().stream().anyMatch(evidence ->
evidence.kind() == Kind.FLOW_CONTINUATION_ROOT));
assertTrue(profile.evidence().stream().anyMatch(evidence ->
evidence.kind() == Kind.LOCAL_USAGE_COMPLETE));
FlowNode parameterNode= new FlowNode(
nodeId,
NodeKind.PARAMETER,
parameterKey,
methodKey,
unit.getHandleIdentifier(),
methodHandle,
0,
true,
parameter.getStartPosition(),
parameter.getLength());
ContainerFlowComponent component= new ContainerFlowComponent(
nodeId,
List.of(parameterNode),
List.of(),
ClosureStatus.LOCAL_CLOSED,
List.of());
ContainerRecommendation recommendation= recommendation(profile);
ContainerSignatureMigrationPlan signatures=
new ContainerSignatureAtomicityPlanner().planClosedSource(
component, resolved, recommendation);
ContainerBridgePolicyPlan bridges=
new ContainerBridgePolicyPlanner().plan(signatures, recommendation);
ContainerMigrationReadiness readiness=
new ContainerMigrationReadinessPlanner().plan(
component, recommendation, signatures, bridges);
var group= signatures.groups().get(0);
var member= group.members().get(0);
var result= new ContainerParameterRewritePlanner().plan(
component, signatures, group, member, profile, readiness);
assertEquals(ExecutionStatus.AUTOMATIC, readiness.status());
assertTrue(result.ready());
assertEquals(parameterKey, result.plan().orElseThrow().parameterBindingKey());
}
private ICompilationUnit createUnit(String source) throws CoreException {
IPackageFragment fragment= context.getSourceFolder()
.createPackageFragment("test", false, null); //$NON-NLS-1$
return fragment.createCompilationUnit("Sample.java", source, true, null); //$NON-NLS-1$
}
private CompilationUnit parse(ICompilationUnit unit) {
ASTParser parser= ASTParser.newParser(AST.getJLSLatest());
parser.setProject(context.getJavaProject());
parser.setSource(unit);
parser.setResolveBindings(true);
parser.setBindingsRecovery(true);
parser.setStatementsRecovery(true);
return (CompilationUnit) parser.createAST(null);
}
private static MethodDeclaration method(CompilationUnit root) {
MethodDeclaration[] result= { null };
root.accept(new ASTVisitor() {
@Override
public boolean visit(MethodDeclaration declaration) {
if ("consume".equals(declaration.getName().getIdentifier())) { //$NON-NLS-1$
result[0]= declaration;
}
return true;
}
});
if (result[0] == null) {
throw new IllegalStateException("Missing consume method"); //$NON-NLS-1$
}
return result[0];
}
private static ContainerRecommendation recommendation(ContainerUsageProfile profile) {
TargetContainerContract target= new TargetContainerContract(
ContainerShape.LIST,
OrderRequirement.ENCOUNTER,
UniquenessRequirement.DUPLICATES_ALLOWED,
Mutability.MUTABLE,
NullContract.ALLOWED,
"Use a dynamic sequence contract."); //$NON-NLS-1$
ContainerRuleDescriptor rule= new ContainerRuleDescriptor(
"semantic.array.append.sequence", //$NON-NLS-1$
ContainerShape.ARRAY,
ContainerShape.LIST,
RuleOwnership.NOVEL,
"", //$NON-NLS-1$
"The migration changes representation and signatures."); //$NON-NLS-1$
return new ContainerRecommendation(
profile,
target,
rule,
Confidence.HIGH,
AutomationLevel.REPORT_ONLY,
List.of(
preserved(ContractProperty.ORDER),
preserved(ContractProperty.UNIQUENESS),
preserved(ContractProperty.MUTABILITY),
preserved(ContractProperty.NULLS),
preserved(ContractProperty.ALIASING),
preserved(ContractProperty.CONCURRENCY),
preserved(ContractProperty.SIGNATURES)));
}
private static ContractAssessment preserved(ContractProperty property) {
return new ContractAssessment(
property,
Preservation.PRESERVED,
property + " is preserved by the closed-source migration."); //$NON-NLS-1$
}
}