GeneratedNameHierarchyPolicy.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.cleanup.multifile;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Modifier;
/** Binding-based hierarchy checks for prospective generated nested type names. */
public final class GeneratedNameHierarchyPolicy {
private GeneratedNameHierarchyPolicy() {
}
/**
* Returns accessible inherited member types whose simple name would be hidden by
* a newly generated nested type in {@code ownerBinding}.
*
* @param ownerBinding the prospective owner type
* @param requestedName the generated nested type name
* @return deterministic qualified collision descriptions
*/
public static List<String> inheritedMemberTypeCollisions(ITypeBinding ownerBinding, String requestedName) {
if (ownerBinding == null || requestedName == null || requestedName.isBlank()) {
return List.of();
}
ITypeBinding owner= ownerBinding.getTypeDeclaration();
Map<String, String> collisions= new TreeMap<>();
Set<String> visited= new HashSet<>();
collect(owner.getSuperclass(), owner, requestedName, visited, collisions);
for (ITypeBinding interfaceBinding : owner.getInterfaces()) {
collect(interfaceBinding, owner, requestedName, visited, collisions);
}
return List.copyOf(collisions.values());
}
private static void collect(ITypeBinding binding, ITypeBinding owner, String requestedName,
Set<String> visited, Map<String, String> collisions) {
if (binding == null) {
return;
}
ITypeBinding declaration= binding.getTypeDeclaration();
String identity= identity(declaration);
if (!visited.add(identity)) {
return;
}
for (ITypeBinding memberType : declaration.getDeclaredTypes()) {
ITypeBinding memberDeclaration= memberType.getTypeDeclaration();
if (requestedName.equals(memberDeclaration.getName()) && isAccessible(memberDeclaration, owner)) {
String memberName= qualifiedName(memberDeclaration);
String declaringName= qualifiedName(declaration);
collisions.put(memberName, memberName + " inherited from " + declaringName); //$NON-NLS-1$
}
}
collect(declaration.getSuperclass(), owner, requestedName, visited, collisions);
for (ITypeBinding interfaceBinding : declaration.getInterfaces()) {
collect(interfaceBinding, owner, requestedName, visited, collisions);
}
}
private static boolean isAccessible(ITypeBinding memberType, ITypeBinding owner) {
int modifiers= memberType.getModifiers();
if (Modifier.isPrivate(modifiers)) {
return false;
}
if (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)) {
return true;
}
String memberPackage= packageName(memberType.getDeclaringClass());
String ownerPackage= packageName(owner);
return memberPackage.equals(ownerPackage);
}
private static String packageName(ITypeBinding binding) {
return binding == null || binding.getPackage() == null ? "" : binding.getPackage().getName(); //$NON-NLS-1$
}
private static String identity(ITypeBinding binding) {
String key= binding.getKey();
return key == null || key.isBlank() ? qualifiedName(binding) : key;
}
private static String qualifiedName(ITypeBinding binding) {
String qualifiedName= binding.getQualifiedName();
if (qualifiedName != null && !qualifiedName.isBlank()) {
return qualifiedName;
}
List<String> names= new ArrayList<>();
ITypeBinding current= binding;
while (current != null) {
names.add(0, current.getName());
current= current.getDeclaringClass();
}
String localName= String.join(".", names); //$NON-NLS-1$
String packageName= packageName(binding);
return packageName.isEmpty() ? localName : packageName + '.' + localName;
}
}