Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.micronaut.annotation.processing.visitor.JavaClassElement Maven / Gradle / Ivy
/*
* Copyright 2017-2022 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.annotation.processing.visitor;
import io.micronaut.annotation.processing.AnnotationUtils;
import io.micronaut.annotation.processing.ModelUtils;
import io.micronaut.annotation.processing.PublicMethodVisitor;
import io.micronaut.annotation.processing.SuperclassAwareTypeVisitor;
import io.micronaut.core.annotation.AccessorsStyle;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.inject.ast.ArrayableClassElement;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.ConstructorElement;
import io.micronaut.inject.ast.ElementModifier;
import io.micronaut.inject.ast.ElementQuery;
import io.micronaut.inject.ast.FieldElement;
import io.micronaut.inject.ast.GenericPlaceholderElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.PackageElement;
import io.micronaut.inject.ast.PropertyElement;
import io.micronaut.inject.ast.WildcardElement;
import io.micronaut.inject.processing.JavaModelUtils;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* A class element returning data from a {@link TypeElement}.
*
* @author James Kleeh
* @author graemerocher
* @since 1.0
*/
@Internal
public class JavaClassElement extends AbstractJavaElement implements ArrayableClassElement {
private static final String KOTLIN_METADATA = "kotlin.Metadata";
private static final String PREFIX_IS = "is";
protected final TypeElement classElement;
protected final JavaVisitorContext visitorContext;
final List extends TypeMirror> typeArguments;
private final int arrayDimensions;
private final boolean isTypeVariable;
private List beanProperties;
private Map> genericTypeInfo;
private List extends Element> enclosedElements;
private String simpleName;
private String name;
private String packageName;
/**
* @param classElement The {@link TypeElement}
* @param annotationMetadata The annotation metadata
* @param visitorContext The visitor context
*/
@Internal
public JavaClassElement(TypeElement classElement, AnnotationMetadata annotationMetadata, JavaVisitorContext visitorContext) {
this(classElement, annotationMetadata, visitorContext, Collections.emptyList(), null, 0, false);
}
/**
* @param classElement The {@link TypeElement}
* @param annotationMetadata The annotation metadata
* @param visitorContext The visitor context
* @param typeArguments The declared type arguments
* @param genericsInfo The generic type info
*/
JavaClassElement(
TypeElement classElement,
AnnotationMetadata annotationMetadata,
JavaVisitorContext visitorContext,
List extends TypeMirror> typeArguments,
Map> genericsInfo) {
this(classElement, annotationMetadata, visitorContext, typeArguments, genericsInfo, 0, false);
}
/**
* @param classElement The {@link TypeElement}
* @param annotationMetadata The annotation metadata
* @param visitorContext The visitor context
* @param typeArguments The declared type arguments
* @param genericsInfo The generic type info
* @param arrayDimensions The number of array dimensions
*/
JavaClassElement(
TypeElement classElement,
AnnotationMetadata annotationMetadata,
JavaVisitorContext visitorContext,
List extends TypeMirror> typeArguments,
Map> genericsInfo,
int arrayDimensions) {
this(classElement, annotationMetadata, visitorContext, typeArguments, genericsInfo, arrayDimensions, false);
}
/**
* @param classElement The {@link TypeElement}
* @param annotationMetadata The annotation metadata
* @param visitorContext The visitor context
* @param typeArguments The declared type arguments
* @param genericsInfo The generic type info
* @param isTypeVariable Is the class element a type variable
*/
JavaClassElement(
TypeElement classElement,
AnnotationMetadata annotationMetadata,
JavaVisitorContext visitorContext,
List extends TypeMirror> typeArguments,
Map> genericsInfo,
boolean isTypeVariable) {
this(classElement, annotationMetadata, visitorContext, typeArguments, genericsInfo, 0, isTypeVariable);
}
/**
* @param classElement The {@link TypeElement}
* @param annotationMetadata The annotation metadata
* @param visitorContext The visitor context
* @param typeArguments The declared type arguments
* @param genericsInfo The generic type info
* @param arrayDimensions The number of array dimensions
* @param isTypeVariable Is the type a type variable
*/
JavaClassElement(
TypeElement classElement,
AnnotationMetadata annotationMetadata,
JavaVisitorContext visitorContext,
List extends TypeMirror> typeArguments,
Map> genericsInfo,
int arrayDimensions,
boolean isTypeVariable) {
super(classElement, annotationMetadata, visitorContext);
this.classElement = classElement;
this.visitorContext = visitorContext;
this.typeArguments = typeArguments;
this.genericTypeInfo = genericsInfo;
this.arrayDimensions = arrayDimensions;
this.isTypeVariable = isTypeVariable;
}
@Override
public boolean isTypeVariable() {
return isTypeVariable;
}
@Override
public String toString() {
return getName();
}
@Override
public boolean isInner() {
return classElement.getNestingKind().isNested();
}
@Override
public boolean isRecord() {
return JavaModelUtils.isRecord(classElement);
}
@NonNull
@Override
public Map getTypeArguments(@NonNull String type) {
if (StringUtils.isNotEmpty(type)) {
Map> data = visitorContext.getGenericUtils().buildGenericTypeArgumentElementInfo(classElement, null, getBoundTypeMirrors());
Map forType = data.get(type);
if (forType != null) {
Map typeArgs = new LinkedHashMap<>(forType.size());
for (Map.Entry entry : forType.entrySet()) {
TypeMirror v = entry.getValue();
ClassElement ce = v != null ? mirrorToClassElement(v, visitorContext, Collections.emptyMap(), visitorContext.getConfiguration().includeTypeLevelAnnotationsInGenericArguments()) : null;
if (ce == null) {
return Collections.emptyMap();
} else {
typeArgs.put(entry.getKey(), ce);
}
}
return Collections.unmodifiableMap(typeArgs);
}
}
return Collections.emptyMap();
}
@Override
public boolean isPrimitive() {
return ClassUtils.getPrimitiveType(getName()).isPresent();
}
@Override
public Collection getInterfaces() {
final List extends TypeMirror> interfaces = classElement.getInterfaces();
if (!interfaces.isEmpty()) {
return Collections.unmodifiableList(interfaces.stream().map((mirror) ->
mirrorToClassElement(mirror, visitorContext, genericTypeInfo)).collect(Collectors.toList())
);
}
return Collections.emptyList();
}
@Override
public Optional getSuperType() {
final TypeMirror superclass = classElement.getSuperclass();
if (superclass != null) {
final Element element = visitorContext.getTypes().asElement(superclass);
if (element instanceof TypeElement) {
TypeElement superElement = (TypeElement) element;
if (!Object.class.getName().equals(superElement.getQualifiedName().toString())) {
// if super type has type arguments, then build a parameterized ClassElement
if (superclass instanceof DeclaredType && !((DeclaredType) superclass).getTypeArguments().isEmpty()) {
return Optional.of(
parameterizedClassElement(
superclass,
visitorContext,
visitorContext.getGenericUtils().buildGenericTypeArgumentElementInfo(classElement, null, getBoundTypeMirrors())));
}
return Optional.of(
new JavaClassElement(
superElement,
visitorContext.getAnnotationUtils().getAnnotationMetadata(superElement),
visitorContext
)
);
}
}
}
return Optional.empty();
}
@Override
public boolean isAbstract() {
return classElement.getModifiers().contains(Modifier.ABSTRACT);
}
@Override
public boolean isInterface() {
return JavaModelUtils.isInterface(classElement);
}
@Override
public List getBeanProperties() {
if (this.beanProperties == null) {
Map props = new LinkedHashMap<>();
Map fields = new LinkedHashMap<>();
if (isRecord()) {
classElement.asType().accept(new SuperclassAwareTypeVisitor(visitorContext) {
@Override
protected boolean isAcceptable(Element element) {
return JavaModelUtils.isRecord(element);
}
@Override
public Object visitDeclared(DeclaredType type, Object o) {
Element element = type.asElement();
if (isAcceptable(element)) {
List extends Element> enclosedElements = element.getEnclosedElements();
for (Element enclosedElement : enclosedElements) {
if (JavaModelUtils.isRecordComponent(enclosedElement) || enclosedElement instanceof ExecutableElement) {
if (enclosedElement.getKind() != ElementKind.CONSTRUCTOR) {
accept(type, enclosedElement, o);
}
}
}
}
return o;
}
@Override
protected void accept(DeclaredType type, Element element, Object o) {
String name = element.getSimpleName().toString();
if (element instanceof ExecutableElement) {
BeanPropertyData beanPropertyData = props.get(name);
if (beanPropertyData != null) {
beanPropertyData.getter = (ExecutableElement) element;
}
} else {
props.computeIfAbsent(name, propertyName -> {
BeanPropertyData beanPropertyData = new BeanPropertyData(propertyName);
beanPropertyData.declaringType = JavaClassElement.this;
beanPropertyData.type = mirrorToClassElement(element.asType(), visitorContext, genericTypeInfo, true);
return beanPropertyData;
});
}
}
}, null);
} else {
classElement.asType().accept(new PublicMethodVisitor(visitorContext) {
final String[] readPrefixes = getValue(AccessorsStyle.class, "readPrefixes", String[].class)
.orElse(new String[]{AccessorsStyle.DEFAULT_READ_PREFIX});
final String[] writePrefixes = getValue(AccessorsStyle.class, "writePrefixes", String[].class)
.orElse(new String[]{AccessorsStyle.DEFAULT_WRITE_PREFIX});
@Override
protected boolean isAcceptable(javax.lang.model.element.Element element) {
if (element.getKind() == ElementKind.FIELD) {
return true;
}
if (element.getKind() == ElementKind.METHOD && element instanceof ExecutableElement) {
Set modifiers = element.getModifiers();
if (modifiers.contains(Modifier.PUBLIC) && !modifiers.contains(Modifier.STATIC)) {
ExecutableElement executableElement = (ExecutableElement) element;
String methodName = executableElement.getSimpleName().toString();
if (methodName.contains("$")) {
return false;
}
if (NameUtils.isReaderName(methodName, readPrefixes) && executableElement.getParameters().isEmpty()) {
return true;
} else {
return NameUtils.isWriterName(methodName, writePrefixes) && executableElement.getParameters().size() == 1;
}
}
}
return false;
}
@Override
protected void accept(DeclaredType declaringType, javax.lang.model.element.Element element, Object o) {
if (element instanceof VariableElement) {
fields.put(element.getSimpleName().toString(), (VariableElement) element);
return;
}
ExecutableElement executableElement = (ExecutableElement) element;
String methodName = executableElement.getSimpleName().toString();
final TypeElement declaringTypeElement = (TypeElement) executableElement.getEnclosingElement();
if (NameUtils.isReaderName(methodName, readPrefixes) && executableElement.getParameters().isEmpty()) {
String propertyName = isKotlinClass(element.getEnclosingElement()) && methodName.startsWith(PREFIX_IS) ?
methodName : NameUtils.getPropertyNameForGetter(methodName, readPrefixes);
TypeMirror returnType = executableElement.getReturnType();
ClassElement getterReturnType;
if (returnType instanceof TypeVariable) {
TypeVariable tv = (TypeVariable) returnType;
final String tvn = tv.toString();
final ClassElement classElement = getTypeArguments().get(tvn);
if (classElement != null) {
getterReturnType = classElement;
} else {
getterReturnType = mirrorToClassElement(returnType, visitorContext, JavaClassElement.this.genericTypeInfo, true);
}
} else {
getterReturnType = mirrorToClassElement(returnType, visitorContext, JavaClassElement.this.genericTypeInfo, true);
}
BeanPropertyData beanPropertyData = props.computeIfAbsent(propertyName, BeanPropertyData::new);
configureDeclaringType(declaringTypeElement, beanPropertyData);
beanPropertyData.type = getterReturnType;
beanPropertyData.getter = executableElement;
if (beanPropertyData.setter != null) {
TypeMirror typeMirror = beanPropertyData.setter.getParameters().get(0).asType();
ClassElement setterParameterType = mirrorToClassElement(typeMirror, visitorContext, JavaClassElement.this.genericTypeInfo, true);
if (!setterParameterType.isAssignable(getterReturnType)) {
beanPropertyData.setter = null; // not a compatible setter
}
}
} else if (NameUtils.isWriterName(methodName, writePrefixes) && executableElement.getParameters().size() == 1) {
String propertyName = NameUtils.getPropertyNameForSetter(methodName, writePrefixes);
TypeMirror typeMirror = executableElement.getParameters().get(0).asType();
ClassElement setterParameterType = mirrorToClassElement(typeMirror, visitorContext, JavaClassElement.this.genericTypeInfo, true);
BeanPropertyData beanPropertyData = props.computeIfAbsent(propertyName, BeanPropertyData::new);
configureDeclaringType(declaringTypeElement, beanPropertyData);
ClassElement propertyType = beanPropertyData.type;
if (propertyType != null) {
if (propertyType.getName().equals(setterParameterType.getName())) {
beanPropertyData.setter = executableElement;
}
} else {
beanPropertyData.setter = executableElement;
}
}
}
private void configureDeclaringType(TypeElement declaringTypeElement, BeanPropertyData beanPropertyData) {
if (beanPropertyData.declaringType == null && !classElement.equals(declaringTypeElement)) {
beanPropertyData.declaringType = mirrorToClassElement(
declaringTypeElement.asType(),
visitorContext,
genericTypeInfo,
true);
} else if (beanPropertyData.declaringType == null) {
beanPropertyData.declaringType = mirrorToClassElement(
declaringTypeElement.asType(),
visitorContext,
genericTypeInfo,
false);
}
}
}, null);
}
if (!props.isEmpty()) {
this.beanProperties = new ArrayList<>(props.size());
for (Map.Entry entry : props.entrySet()) {
String propertyName = entry.getKey();
BeanPropertyData value = entry.getValue();
final VariableElement fieldElement = fields.get(propertyName);
if (value.getter != null) {
final AnnotationMetadata annotationMetadata;
List parents = new ArrayList<>();
if (fieldElement != null) {
parents.add(fieldElement);
}
if (value.setter != null) {
parents.add(value.setter);
}
if (!parents.isEmpty()) {
annotationMetadata = visitorContext.getAnnotationUtils().getAnnotationMetadata(parents, value.getter);
} else {
annotationMetadata = visitorContext
.getAnnotationUtils()
.newAnnotationBuilder().buildForMethod(value.getter);
}
JavaPropertyElement propertyElement = new JavaPropertyElement(
value.declaringType == null ? this : value.declaringType,
value.getter,
annotationMetadata,
propertyName,
value.type,
value.setter == null,
visitorContext) {
@Override
public ClassElement getGenericType() {
TypeMirror propertyType = value.getter.getReturnType();
Map> declaredGenericInfo = getGenericTypeInfo();
ClassElement typeElement = parameterizedClassElement(propertyType, visitorContext, declaredGenericInfo);
if (typeElement instanceof JavaClassElement && fieldElement != null) {
TypeMirror fieldType = fieldElement.asType();
if (visitorContext.getTypes().isAssignable(fieldType, propertyType)) {
ClassElement fieldElement = parameterizedClassElement(fieldType, visitorContext, declaredGenericInfo);
int typeGenericsSize = typeElement.getBoundGenericTypes().size();
if (fieldElement instanceof JavaClassElement
&& typeGenericsSize > 0 && typeGenericsSize == fieldElement.getBoundGenericTypes().size()) {
return ((JavaClassElement) typeElement).withBoundGenericTypeMirrors(((JavaClassElement) fieldElement).typeArguments);
}
}
}
return typeElement;
}
@Override
public Optional getDocumentation() {
Elements elements = visitorContext.getElements();
String docComment = elements.getDocComment(value.getter);
return Optional.ofNullable(docComment);
}
@Override
public Optional getWriteMethod() {
if (value.setter != null) {
return Optional.of(new JavaMethodElement(
JavaClassElement.this,
value.setter,
visitorContext.getAnnotationUtils().newAnnotationBuilder().buildForMethod(value.setter),
visitorContext
));
}
return Optional.empty();
}
@Override
public Optional getReadMethod() {
return Optional.of(new JavaMethodElement(
JavaClassElement.this,
value.getter,
annotationMetadata,
visitorContext
));
}
};
beanProperties.add(propertyElement);
}
}
this.beanProperties = Collections.unmodifiableList(beanProperties);
} else {
this.beanProperties = Collections.emptyList();
}
}
return Collections.unmodifiableList(beanProperties);
}
private boolean isKotlinClass(Element element) {
return element.getAnnotationMirrors().stream().anyMatch(am -> am.getAnnotationType().asElement().toString().equals(KOTLIN_METADATA));
}
@Override
public List getEnclosedElements(@NonNull ElementQuery query) {
Objects.requireNonNull(query, "Query cannot be null");
ElementQuery.Result result = query.result();
ElementKind kind = getElementKind(result.getElementType());
List resultingElements = new ArrayList<>();
List enclosedElements = new ArrayList<>(getDeclaredEnclosedElements());
boolean onlyDeclared = result.isOnlyDeclared();
boolean onlyAbstract = result.isOnlyAbstract();
boolean onlyConcrete = result.isOnlyConcrete();
boolean onlyInstance = result.isOnlyInstance();
boolean includeEnumConstants = result.isIncludeEnumConstants();
boolean includeOverriddenMethods = result.isIncludeOverriddenMethods();
boolean includeHiddenElements = result.isIncludeHiddenElements();
if (!onlyDeclared) {
Elements elements = visitorContext.getElements();
TypeMirror superclass = classElement.getSuperclass();
// traverse the super class true and add elements that are not overridden
while (superclass instanceof DeclaredType) {
DeclaredType dt = (DeclaredType) superclass;
TypeElement element = (TypeElement) dt.asElement();
// reached non-accessible class like Object, Enum, Record etc.
if (element.getQualifiedName().toString().startsWith("java.lang.")) {
break;
}
List extends Element> superElements = element.getEnclosedElements();
List elementsToAdd = new ArrayList<>(superElements.size());
superElements:
for (Element superElement : superElements) {
ElementKind superKind = superElement.getKind();
if (superKind == kind) {
for (Element enclosedElement : enclosedElements) {
if (!includeHiddenElements && elements.hides(enclosedElement, superElement)) {
continue superElements;
} else if (enclosedElement.getKind() == ElementKind.METHOD && superElement.getKind() == ElementKind.METHOD) {
final ExecutableElement methodCandidate = (ExecutableElement) superElement;
if (!includeOverriddenMethods && elements.overrides((ExecutableElement) enclosedElement, methodCandidate, this.classElement)) {
continue superElements;
}
}
}
// dependency injection method resolution requires extended overrides checks
if (result.isOnlyInjected() && superElement.getKind() == ElementKind.METHOD) {
final ExecutableElement methodCandidate = (ExecutableElement) superElement;
// check for extended override
final String thisClassName = this.classElement.getQualifiedName().toString();
final String declaringClassName = element.getQualifiedName().toString();
boolean isParent = !declaringClassName.equals(thisClassName);
final ModelUtils javaModelUtils = visitorContext.getModelUtils();
final ExecutableElement overridingMethod = javaModelUtils
.overridingOrHidingMethod(methodCandidate, this.classElement, false)
.orElse(methodCandidate);
TypeElement overridingClass = javaModelUtils.classElementFor(overridingMethod);
boolean overridden = isParent && overridingClass != null &&
!overridingClass.getQualifiedName().toString().equals(declaringClassName);
boolean isPackagePrivate = javaModelUtils.isPackagePrivate(methodCandidate);
boolean isPrivate = methodCandidate.getModifiers().contains(Modifier.PRIVATE);
if (overridden && !(isPrivate || isPackagePrivate)) {
// bail out if the method has been overridden, since it will have already been handled
continue;
}
if (isParent && overridden) {
boolean overriddenInjected = overridden && visitorContext.getAnnotationUtils()
.getAnnotationMetadata(overridingMethod).hasDeclaredAnnotation(
AnnotationUtil.INJECT);
String packageOfOverridingClass = visitorContext.getElements().getPackageOf(overridingMethod).getQualifiedName().toString();
String packageOfDeclaringClass = visitorContext.getElements().getPackageOf(element).getQualifiedName().toString();
boolean isPackagePrivateAndPackagesDiffer = overridden && isPackagePrivate &&
!packageOfOverridingClass.equals(packageOfDeclaringClass);
if (!overriddenInjected && !isPackagePrivateAndPackagesDiffer && !isPrivate) {
// bail out if the overridden method is package private and in the same package
// and is not annotated with @Inject
continue;
}
}
}
if (onlyAbstract && !superElement.getModifiers().contains(Modifier.ABSTRACT)) {
continue;
} else if (onlyConcrete && superElement.getModifiers().contains(Modifier.ABSTRACT)) {
continue;
} else if (onlyInstance && superElement.getModifiers().contains(Modifier.STATIC)) {
continue;
}
elementsToAdd.add(superElement);
}
}
enclosedElements.addAll(elementsToAdd);
superclass = element.getSuperclass();
}
if (kind == ElementKind.METHOD || kind == ElementKind.FIELD) {
// if the element kind is interfaces then we need to go through interfaces as well
Set allInterfaces = visitorContext.getModelUtils().getAllInterfaces(this.classElement);
Collection interfacesToProcess = new ArrayList<>(allInterfaces.size());
// Remove duplicates
outer:
for (TypeElement el : allInterfaces) {
for (TypeElement existingEl : interfacesToProcess) {
Name qualifiedName = existingEl.getQualifiedName();
if (qualifiedName.equals(el.getQualifiedName())) {
continue outer;
}
}
interfacesToProcess.add(el);
}
List elementsToAdd = new ArrayList<>(allInterfaces.size());
for (TypeElement itfe : interfacesToProcess) {
List extends Element> interfaceElements = itfe.getEnclosedElements();
interfaceElements:
for (Element interfaceElement : interfaceElements) {
if (interfaceElement.getKind() == ElementKind.METHOD) {
ExecutableElement ee = (ExecutableElement) interfaceElement;
if (onlyAbstract && ee.getModifiers().contains(Modifier.DEFAULT)) {
continue;
} else if (onlyConcrete && !ee.getModifiers().contains(Modifier.DEFAULT)) {
continue;
}
for (Element enclosedElement : enclosedElements) {
if (enclosedElement.getKind() == ElementKind.METHOD) {
if (!includeOverriddenMethods && elements.overrides((ExecutableElement) enclosedElement, ee, this.classElement)) {
continue interfaceElements;
}
}
}
}
elementsToAdd.add(interfaceElement);
}
}
enclosedElements.addAll(elementsToAdd);
elementsToAdd.clear();
}
if (onlyAbstract) {
if (isInterface()) {
enclosedElements.removeIf((e) -> e.getModifiers().contains(Modifier.DEFAULT));
} else {
enclosedElements.removeIf((e) -> !e.getModifiers().contains(Modifier.ABSTRACT));
}
} else if (onlyConcrete) {
if (isInterface()) {
enclosedElements.removeIf((e) -> !e.getModifiers().contains(Modifier.DEFAULT));
} else {
enclosedElements.removeIf((e) -> e.getModifiers().contains(Modifier.ABSTRACT));
}
}
}
if (onlyInstance) {
enclosedElements.removeIf((e) -> e.getModifiers().contains(Modifier.STATIC));
}
List>> modifierPredicates = result.getModifierPredicates();
List> namePredicates = result.getNamePredicates();
List> annotationPredicates = result.getAnnotationPredicates();
final List> typePredicates = result.getTypePredicates();
boolean hasNamePredicates = !namePredicates.isEmpty();
boolean hasModifierPredicates = !modifierPredicates.isEmpty();
boolean hasAnnotationPredicates = !annotationPredicates.isEmpty();
boolean hasTypePredicates = !typePredicates.isEmpty();
boolean onlyAccessible = result.isOnlyAccessible();
final JavaElementFactory elementFactory = visitorContext.getElementFactory();
elementLoop:
for (Element enclosedElement : enclosedElements) {
ElementKind enclosedElementKind = enclosedElement.getKind();
if (enclosedElementKind == kind
|| includeEnumConstants && kind == ElementKind.FIELD && enclosedElementKind == ElementKind.ENUM_CONSTANT
|| (enclosedElementKind == ElementKind.ENUM && kind == ElementKind.CLASS)) {
String elementName = enclosedElement.getSimpleName().toString();
if (onlyAccessible) {
// exclude private members
if (enclosedElement.getModifiers().contains(Modifier.PRIVATE)) {
continue;
} else if (elementName.startsWith("$")) {
// exclude synthetic members or bridge methods that start with $
continue;
} else {
Element enclosingElement = enclosedElement.getEnclosingElement();
final ClassElement onlyAccessibleFrom = result.getOnlyAccessibleFromType().orElse(this);
Object accessibleFrom = onlyAccessibleFrom.getNativeType();
// if the outer element of the enclosed element is not the current class
// we need to check if it package private and within a different package so it can be excluded
if (enclosingElement != accessibleFrom && visitorContext.getModelUtils().isPackagePrivate(enclosedElement)) {
if (enclosingElement instanceof TypeElement) {
Name qualifiedName = ((TypeElement) enclosingElement).getQualifiedName();
String packageName = NameUtils.getPackageName(qualifiedName.toString());
if (!packageName.equals(onlyAccessibleFrom.getPackageName())) {
continue;
}
}
}
}
}
if (hasModifierPredicates) {
Set modifiers = enclosedElement
.getModifiers().stream().map(m -> ElementModifier.valueOf(m.name())).collect(Collectors.toSet());
for (Predicate> modifierPredicate : modifierPredicates) {
if (!modifierPredicate.test(modifiers)) {
continue elementLoop;
}
}
}
if (hasNamePredicates) {
for (Predicate namePredicate : namePredicates) {
if (!namePredicate.test(elementName)) {
continue elementLoop;
}
}
}
final AnnotationMetadata metadata = visitorContext.getAnnotationUtils().getAnnotationMetadata(enclosedElement);
if (hasAnnotationPredicates) {
for (Predicate annotationPredicate : annotationPredicates) {
if (!annotationPredicate.test(metadata)) {
continue elementLoop;
}
}
}
T element;
switch (enclosedElementKind) {
case METHOD:
final ExecutableElement executableElement = (ExecutableElement) enclosedElement;
//noinspection unchecked
element = (T) elementFactory.newMethodElement(
this,
executableElement,
metadata,
genericTypeInfo
);
break;
case FIELD:
case ENUM_CONSTANT:
//noinspection unchecked
element = (T) elementFactory.newFieldElement(
this,
(VariableElement) enclosedElement,
metadata
);
break;
case CONSTRUCTOR:
//noinspection unchecked
element = (T) elementFactory.newConstructorElement(
this,
(ExecutableElement) enclosedElement,
metadata
);
break;
case CLASS:
case ENUM:
//noinspection unchecked
element = (T) elementFactory.newClassElement(
(TypeElement) enclosedElement,
metadata
);
break;
default:
element = null;
}
if (element != null) {
if (hasTypePredicates) {
for (Predicate typePredicate : typePredicates) {
ClassElement classElement;
if (element instanceof ConstructorElement) {
classElement = this;
} else if (element instanceof MethodElement) {
classElement = ((MethodElement) element).getGenericReturnType();
} else if (element instanceof ClassElement) {
classElement = (ClassElement) element;
} else {
classElement = ((FieldElement) element).getGenericField();
}
if (!typePredicate.test(classElement)) {
continue elementLoop;
}
}
}
List> elementPredicates = result.getElementPredicates();
if (!elementPredicates.isEmpty()) {
for (Predicate elementPredicate : elementPredicates) {
if (!elementPredicate.test(element)) {
continue elementLoop;
}
}
}
resultingElements.add(element);
}
}
}
return Collections.unmodifiableList(resultingElements);
}
private List extends Element> getDeclaredEnclosedElements() {
if (this.enclosedElements == null) {
this.enclosedElements = classElement.getEnclosedElements();
}
return this.enclosedElements;
}
private ElementKind getElementKind(Class elementType) {
if (elementType == MethodElement.class) {
return ElementKind.METHOD;
} else if (elementType == FieldElement.class) {
return ElementKind.FIELD;
} else if (elementType == ConstructorElement.class) {
return ElementKind.CONSTRUCTOR;
} else if (elementType == ClassElement.class) {
return ElementKind.CLASS;
}
throw new IllegalArgumentException("Unsupported element type for query: " + elementType);
}
@Override
public boolean isArray() {
return arrayDimensions > 0;
}
@Override
public int getArrayDimensions() {
return arrayDimensions;
}
@Override
public ClassElement withArrayDimensions(int arrayDimensions) {
if (arrayDimensions == this.arrayDimensions) {
return this;
}
return new JavaClassElement(classElement, getAnnotationMetadata(), visitorContext, typeArguments, getGenericTypeInfo(), arrayDimensions, false);
}
@Override
public String getSimpleName() {
if (simpleName == null) {
simpleName = JavaModelUtils.getClassNameWithoutPackage(classElement);
}
return simpleName;
}
@Override
public String getName() {
if (name == null) {
name = JavaModelUtils.getClassName(classElement);
}
return name;
}
@Override
public String getPackageName() {
if (packageName == null) {
packageName = JavaModelUtils.getPackageName(classElement);
}
return packageName;
}
@Override
public PackageElement getPackage() {
Element enclosingElement = classElement.getEnclosingElement();
while (enclosingElement != null && enclosingElement.getKind() != ElementKind.PACKAGE) {
enclosingElement = enclosingElement.getEnclosingElement();
}
if (enclosingElement instanceof javax.lang.model.element.PackageElement) {
return new JavaPackageElement(
((javax.lang.model.element.PackageElement) enclosingElement),
visitorContext.getAnnotationUtils().getAnnotationMetadata(enclosingElement),
visitorContext
);
} else {
return PackageElement.DEFAULT_PACKAGE;
}
}
@Override
public boolean isAssignable(String type) {
TypeElement otherElement = visitorContext.getElements().getTypeElement(type);
if (otherElement != null) {
Types types = visitorContext.getTypes();
TypeMirror thisType = types.erasure(classElement.asType());
TypeMirror thatType = types.erasure(otherElement.asType());
return types.isAssignable(thisType, thatType);
}
return false;
}
@Override
public boolean isAssignable(ClassElement type) {
if (type.isPrimitive()) {
return isAssignable(type.getName());
} else {
Object nativeType = type.getNativeType();
if (nativeType instanceof TypeElement) {
Types types = visitorContext.getTypes();
TypeMirror thisType = types.erasure(classElement.asType());
TypeMirror thatType = types.erasure(((TypeElement) nativeType).asType());
return types.isAssignable(thisType, thatType);
}
}
return false;
}
@NonNull
@Override
public Optional getPrimaryConstructor() {
final AnnotationUtils annotationUtils = visitorContext.getAnnotationUtils();
final ModelUtils modelUtils = visitorContext.getModelUtils();
ExecutableElement method = modelUtils.staticCreatorFor(classElement, annotationUtils);
if (method == null) {
if (isInner() && !isStatic()) {
// only static inner classes can be constructed
return Optional.empty();
}
method = modelUtils.concreteConstructorFor(classElement, annotationUtils);
}
return createMethodElement(annotationUtils, method);
}
@Override
public Optional getDefaultConstructor() {
final AnnotationUtils annotationUtils = visitorContext.getAnnotationUtils();
final ModelUtils modelUtils = visitorContext.getModelUtils();
ExecutableElement method = modelUtils.defaultStaticCreatorFor(classElement, annotationUtils);
if (method == null) {
if (isInner() && !isStatic()) {
// only static inner classes can be constructed
return Optional.empty();
}
method = modelUtils.defaultConstructorFor(classElement);
}
return createMethodElement(annotationUtils, method);
}
@Override
public Optional getEnclosingType() {
if (isInner()) {
Element enclosingElement = this.classElement.getEnclosingElement();
if (enclosingElement instanceof TypeElement) {
return Optional.of(visitorContext.getElementFactory().newClassElement(
((TypeElement) enclosingElement),
visitorContext.getAnnotationUtils().getAnnotationMetadata(enclosingElement)
));
}
}
return Optional.empty();
}
private Optional createMethodElement(AnnotationUtils annotationUtils, ExecutableElement method) {
return Optional.ofNullable(method).map(executableElement -> {
final AnnotationMetadata annotationMetadata = annotationUtils.getAnnotationMetadata(executableElement);
if (executableElement.getKind() == ElementKind.CONSTRUCTOR) {
return new JavaConstructorElement(this, executableElement, annotationMetadata, visitorContext);
} else {
return new JavaMethodElement(this, executableElement, annotationMetadata, visitorContext);
}
});
}
@NonNull
@Override
public List getBoundGenericTypes() {
return typeArguments.stream()
//return getGenericTypeInfo().getOrDefault(classElement.getQualifiedName().toString(), Collections.emptyMap()).values().stream()
.map(tm -> mirrorToClassElement(tm, visitorContext, getGenericTypeInfo()))
.collect(Collectors.toList());
}
@NonNull
@Override
public List extends GenericPlaceholderElement> getDeclaredGenericPlaceholders() {
return classElement.getTypeParameters().stream()
// we want the *declared* variables, so we don't pass in our genericsInfo.
.map(tpe -> (GenericPlaceholderElement) mirrorToClassElement(tpe.asType(), visitorContext))
.collect(Collectors.toList());
}
@NonNull
@Override
public ClassElement getRawClassElement() {
return visitorContext.getElementFactory().newClassElement(classElement, visitorContext.getAnnotationUtils().getAnnotationMetadata(classElement))
.withArrayDimensions(getArrayDimensions());
}
private static TypeMirror toTypeMirror(JavaVisitorContext visitorContext, ClassElement element) {
if (element.isArray()) {
return visitorContext.getTypes().getArrayType(toTypeMirror(visitorContext, element.fromArray()));
} else if (element.isWildcard()) {
WildcardElement wildcardElement = (WildcardElement) element;
List extends ClassElement> upperBounds = wildcardElement.getUpperBounds();
if (upperBounds.size() != 1) {
throw new UnsupportedOperationException("Multiple upper bounds not supported");
}
TypeMirror upperBound = toTypeMirror(visitorContext, upperBounds.get(0));
if (upperBound.toString().equals("java.lang.Object")) {
upperBound = null;
}
List extends ClassElement> lowerBounds = wildcardElement.getLowerBounds();
if (lowerBounds.size() > 1) {
throw new UnsupportedOperationException("Multiple upper bounds not supported");
}
TypeMirror lowerBound = lowerBounds.isEmpty() ? null : toTypeMirror(visitorContext, lowerBounds.get(0));
return visitorContext.getTypes().getWildcardType(upperBound, lowerBound);
} else if (element.isGenericPlaceholder()) {
if (!(element instanceof JavaGenericPlaceholderElement)) {
throw new UnsupportedOperationException("Free type variable on non-java class");
}
return ((JavaGenericPlaceholderElement) element).realTypeVariable;
} else {
if (element instanceof JavaClassElement) {
return visitorContext.getTypes().getDeclaredType(
((JavaClassElement) element).classElement,
((JavaClassElement) element).typeArguments.toArray(new TypeMirror[0]));
} else {
return visitorContext.getTypes().getDeclaredType(
((JavaClassElement) visitorContext.getClassElement(element.getName()).get()).classElement,
element.getBoundGenericTypes().stream().map(ce -> toTypeMirror(visitorContext, ce)).toArray(TypeMirror[]::new));
}
}
}
@NonNull
@Override
public ClassElement withBoundGenericTypes(@NonNull List extends ClassElement> typeArguments) {
if (typeArguments.isEmpty() && this.typeArguments.isEmpty()) {
return this;
}
List typeMirrors = typeArguments.stream()
.map(ce -> toTypeMirror(visitorContext, ce))
.collect(Collectors.toList());
return withBoundGenericTypeMirrors(typeMirrors);
}
private ClassElement withBoundGenericTypeMirrors(@NonNull List extends TypeMirror> typeMirrors) {
if (typeMirrors.equals(this.typeArguments)) {
return this;
}
Map boundByName = new LinkedHashMap<>();
Iterator extends TypeParameterElement> tpes = classElement.getTypeParameters().iterator();
Iterator extends TypeMirror> args = typeMirrors.iterator();
while (tpes.hasNext() && args.hasNext()) {
boundByName.put(tpes.next().getSimpleName().toString(), args.next());
}
Map> genericsInfo = visitorContext.getGenericUtils().buildGenericTypeArgumentElementInfo(classElement, null, boundByName);
return new JavaClassElement(classElement, getAnnotationMetadata(), visitorContext, typeMirrors, genericsInfo, arrayDimensions);
}
@Override
public @NonNull
Map getTypeArguments() {
List extends TypeParameterElement> typeParameters = classElement.getTypeParameters();
Iterator extends TypeParameterElement> tpi = typeParameters.iterator();
Map map = new LinkedHashMap<>();
while (tpi.hasNext()) {
TypeParameterElement tpe = tpi.next();
ClassElement classElement = mirrorToClassElement(tpe.asType(), visitorContext, this.genericTypeInfo, visitorContext.getConfiguration().includeTypeLevelAnnotationsInGenericArguments());
map.put(tpe.toString(), classElement);
}
return Collections.unmodifiableMap(map);
}
private Map getBoundTypeMirrors() {
List extends TypeParameterElement> typeParameters = classElement.getTypeParameters();
Iterator extends TypeParameterElement> tpi = typeParameters.iterator();
Map map = new LinkedHashMap<>();
while (tpi.hasNext()) {
TypeParameterElement tpe = tpi.next();
TypeMirror t = tpe.asType();
map.put(tpe.toString(), t);
}
return Collections.unmodifiableMap(map);
}
@NonNull
@Override
public Map> getAllTypeArguments() {
Map typeArguments = getBoundTypeMirrors();
Map> info = visitorContext.getGenericUtils()
.buildGenericTypeArgumentElementInfo(
classElement,
null,
typeArguments
);
Map> result = new LinkedHashMap<>(info.size());
info.forEach((name, generics) -> {
Map resolved = new LinkedHashMap<>(generics.size());
generics.forEach((variable, mirror) -> {
final Map typeInfo = this.genericTypeInfo != null ? this.genericTypeInfo.get(getName()) : null;
TypeMirror resolvedType = mirror;
if (mirror instanceof TypeVariable && typeInfo != null) {
final TypeMirror tm = typeInfo.get(mirror.toString());
if (tm != null) {
resolvedType = tm;
}
}
ClassElement classElement = mirrorToClassElement(
resolvedType,
visitorContext,
info,
visitorContext.getConfiguration().includeTypeLevelAnnotationsInGenericArguments(),
mirror instanceof TypeVariable
);
resolved.put(variable, classElement);
});
result.put(name, resolved);
});
if (!typeArguments.isEmpty()) {
result.put(JavaModelUtils.getClassName(this.classElement), getTypeArguments());
}
return result;
}
/**
* @return The generic type info for this class.
*/
Map> getGenericTypeInfo() {
if (genericTypeInfo == null) {
genericTypeInfo = visitorContext.getGenericUtils().buildGenericTypeArgumentElementInfo(classElement, null, getBoundTypeMirrors());
}
return genericTypeInfo;
}
/**
* Internal holder class for getters and setters.
*/
private static class BeanPropertyData {
ClassElement type;
ClassElement declaringType;
ExecutableElement getter;
ExecutableElement setter;
final String propertyName;
public BeanPropertyData(String propertyName) {
this.propertyName = propertyName;
}
}
}