IntEnumPackageVisibilityPolicy.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.internal.corext.fix.multifile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IPackageFragment;
import org.sandbox.jdt.cleanup.multifile.MultiFileCandidateDiagnostic;
import org.sandbox.jdt.cleanup.multifile.MultiFileCandidateOutcome;
import org.sandbox.jdt.cleanup.multifile.MultiFileCleanUpDiagnostics;
import org.sandbox.jdt.cleanup.multifile.MultiFileCleanUpPlanResult;
/** Planning-time accessibility policy for generated package-private nested enums. */
public final class IntEnumPackageVisibilityPolicy {
private IntEnumPackageVisibilityPolicy() {
}
/**
* Removes candidates with a caller outside the owner package and replaces the
* transformed diagnostic with a deterministic rejection.
*/
public static MultiFileCleanUpPlanResult<IntEnumMigrationPlan> enforce(
MultiFileCleanUpPlanResult<IntEnumMigrationPlan> result, ICompilationUnit[] compilationUnits) {
if (result.plan() == null || result.plan().candidates().isEmpty()) {
return result;
}
Map<String, ICompilationUnit> unitsByHandle= new LinkedHashMap<>();
Arrays.stream(compilationUnits).map(ICompilationUnit::getPrimary)
.forEach(unit -> unitsByHandle.put(unit.getHandleIdentifier(), unit));
List<IntEnumCandidate> accepted= new ArrayList<>();
List<MultiFileCandidateDiagnostic> diagnostics= new ArrayList<>(result.diagnostics().candidates());
for (IntEnumCandidate candidate : result.plan().candidates()) {
ICompilationUnit owner= unitsByHandle.get(candidate.ownerCompilationUnitHandle());
String ownerPackage= owner == null ? null : packageName(owner);
Set<String> crossPackageCallers= new LinkedHashSet<>();
for (String handle : candidate.expectedCallCountsByUnit().keySet()) {
if (handle.equals(candidate.ownerCompilationUnitHandle())) {
continue;
}
ICompilationUnit caller= unitsByHandle.get(handle);
if (ownerPackage == null || caller == null || !ownerPackage.equals(packageName(caller))) {
crossPackageCallers.add(handle);
}
}
if (ownerPackage != null && crossPackageCallers.isEmpty()) {
accepted.add(candidate);
continue;
}
Set<String> expectedRelatedHandles= new LinkedHashSet<>();
expectedRelatedHandles.add(candidate.ownerCompilationUnitHandle());
expectedRelatedHandles.addAll(candidate.expectedCallCountsByUnit().keySet());
MultiFileCandidateDiagnostic transformedDiagnostic= diagnostics.stream()
.filter(diagnostic -> diagnostic.outcome() == MultiFileCandidateOutcome.TRANSFORMED)
.filter(diagnostic -> diagnostic.ownerCompilationUnitHandle()
.equals(candidate.ownerCompilationUnitHandle()))
.filter(diagnostic -> Set.copyOf(diagnostic.relatedCompilationUnitHandles())
.equals(Set.copyOf(expectedRelatedHandles)))
.findFirst()
.orElseThrow(() -> new IllegalStateException(
"Missing transformed diagnostic for planned int-to-enum candidate")); //$NON-NLS-1$
diagnostics.removeIf(diagnostic -> diagnostic.outcome() == MultiFileCandidateOutcome.TRANSFORMED
&& diagnostic.candidateId().equals(transformedDiagnostic.candidateId()));
List<String> related= new ArrayList<>();
related.add(candidate.ownerCompilationUnitHandle());
related.addAll(crossPackageCallers);
diagnostics.add(MultiFileCandidateDiagnostic.rejected(transformedDiagnostic.candidateId(),
candidate.ownerCompilationUnitHandle(), "CROSS_PACKAGE_CALLER", //$NON-NLS-1$
ownerPackage == null
? "The owner compilation unit could not be resolved for package visibility validation." //$NON-NLS-1$
: "The generated nested enum is package-private, but at least one caller is outside package " //$NON-NLS-1$
+ printablePackage(ownerPackage)
+ ". Select a compatibility-managed migration instead.", //$NON-NLS-1$
related));
}
IntEnumMigrationPlan filteredPlan= new IntEnumMigrationPlan(result.plan().selectedScope(), accepted);
MultiFileCleanUpDiagnostics filteredDiagnostics= new MultiFileCleanUpDiagnostics(
result.diagnostics().cleanupId(), result.diagnostics().scope(), diagnostics);
return MultiFileCleanUpPlanResult.success(filteredPlan, result.status(), result.metrics(), filteredDiagnostics);
}
private static String packageName(ICompilationUnit unit) {
return unit.getParent() instanceof IPackageFragment fragment ? fragment.getElementName() : ""; //$NON-NLS-1$
}
private static String printablePackage(String packageName) {
return packageName.isEmpty() ? "<default>" : packageName; //$NON-NLS-1$
}
}