VisibilityService.java
/*
* SPDX-FileCopyrightText: 2025 kaumei.io
* SPDX-License-Identifier: Apache-2.0
*/
package io.kaumei.jdbc.anno.model;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.Elements;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Set;
public final class VisibilityService {
enum VisibilityKind {
PUBLIC,
PACKAGE,
NOT_VISIBLE;
static VisibilityKind of(Element element) {
var modifiers = element.getModifiers();
if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.PROTECTED)) {
return VisibilityKind.NOT_VISIBLE;
} else if (modifiers.contains(Modifier.PUBLIC)) {
return VisibilityKind.PUBLIC;
}
return VisibilityKind.PACKAGE;
}
VisibilityKind and(Element elem) {
var other = VisibilityKind.of(elem);
if (this == NOT_VISIBLE || other == NOT_VISIBLE) {
return NOT_VISIBLE;
} else if (this == PUBLIC) {
return other;
} else if (other == PUBLIC) {
return this;
}
return PACKAGE;
}
}
// ------------------------------------------------------------------------
private final Elements elements;
public VisibilityService(Elements elements) {
this.elements = elements;
}
public boolean isVisible(Element from, Visibility v) {
return v.isPublic() || v.isVisible(this.elements.getPackageOf(from));
}
// ------------------------------------------------------------------------
public Visibility ofElement(Element elem) {
var visibility = VisibilityKind.of(elem);
var current = elem.getEnclosingElement();
while (current instanceof TypeElement type) {
visibility = visibility.and(type);
current = current.getEnclosingElement();
}
if (visibility != VisibilityKind.NOT_VISIBLE && current instanceof PackageElement pkg) {
if (visibility == VisibilityKind.PUBLIC) {
return Visibility.PUBLIC_VISIBILITY;
}
return Visibility.packageAccess(pkg);
}
return Visibility.NO_VISIBLE;
}
public Visibility ofMethod(ExecutableElement executable) {
var result = ofElement(executable);
result = result.and(ofTypeMirror(executable.getReturnType()));
for (VariableElement parameter : executable.getParameters()) {
result = result.and(ofTypeMirror(parameter.asType()));
}
for (TypeParameterElement parameter : executable.getTypeParameters()) {
for (TypeMirror bound : parameter.getBounds()) {
result = result.and(ofTypeMirror(bound));
}
}
return result;
}
// ------------------------------------------------------------------------
private Visibility ofTypeMirror(TypeMirror type) {
Set<TypeMirror> visited = Collections.newSetFromMap(new IdentityHashMap<>());
return of(type, visited);
}
private Visibility of(TypeMirror type, Set<TypeMirror> visited) {
if (!visited.add(type)) {
return Visibility.PUBLIC_VISIBILITY;
}
return switch (type.getKind()) {
case ARRAY -> of(((ArrayType) type).getComponentType(), visited);
case DECLARED, ERROR -> {
DeclaredType declared = (DeclaredType) type;
Visibility result = ofElement(declared.asElement());
TypeMirror enclosingType = declared.getEnclosingType();
if (enclosingType.getKind() != TypeKind.NONE) {
result = result.and(of(enclosingType, visited));
}
for (TypeMirror argument : declared.getTypeArguments()) {
result = result.and(of(argument, visited));
}
yield result;
}
case TYPEVAR -> {
TypeVariable variable = (TypeVariable) type;
yield of(variable.getUpperBound(), visited)
.and(of(variable.getLowerBound(), visited));
}
case WILDCARD -> {
WildcardType wildcard = (WildcardType) type;
Visibility result = Visibility.PUBLIC_VISIBILITY;
if (wildcard.getExtendsBound() != null) {
result = result.and(of(wildcard.getExtendsBound(), visited));
}
if (wildcard.getSuperBound() != null) {
result = result.and(of(wildcard.getSuperBound(), visited));
}
yield result;
}
case INTERSECTION -> {
Visibility result = Visibility.PUBLIC_VISIBILITY;
for (TypeMirror bound : ((IntersectionType) type).getBounds()) {
result = result.and(of(bound, visited));
}
yield result;
}
default -> Visibility.PUBLIC_VISIBILITY;
};
}
}