package rocks.inspectit.server.instrumentation.config;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import rocks.inspectit.server.instrumentation.classcache.ClassCache;
import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableAnnotationType;
import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableClassType;
import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableInterfaceType;
import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableMethodType;
import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableType;
import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableTypeWithAnnotations;
import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableTypeWithMethods;
import rocks.inspectit.shared.all.pattern.WildcardMatchPattern;
import rocks.inspectit.shared.cs.ci.assignment.AbstractClassSensorAssignment;
import rocks.inspectit.shared.cs.ci.assignment.impl.MethodSensorAssignment;
/**
* This class helps in getting the class types that fit the class sensor assignment.
*
* @author Ivan Senic
*
*/
@Component
public class ClassCacheSearchNarrower {
/**
* Helps in the search for the {@link ImmutableClassType} that fit the given
* {@link AbstractClassSensorAssignment}.
* <p>
* Search order is following:
* <p>
* 1. If direct class/interface/super-class name is given, then by name <br>
* 2. If annotation is given, then by annotation<br>
* 3. If nothing of above, then by the wild card name search.
*
* @param classCache
* {@link ClassCache} to look in.
* @param classSensorAssignment
* {@link AbstractClassSensorAssignment}
* @return All initialized {@link ImmutableClassType} that might match given
* {@link AbstractClassSensorAssignment}. Note that this is only narrow process, full
* check must be performed on the returned results.
*/
public Collection<? extends ImmutableClassType> narrowByClassSensorAssignment(ClassCache classCache, AbstractClassSensorAssignment<?> classSensorAssignment) {
if (!WildcardMatchPattern.isPattern(classSensorAssignment.getClassName())) {
// if we don't have a pattern that just load
return narrowByNameSearch(classCache, classSensorAssignment.getClassName(), classSensorAssignment.isInterf(), classSensorAssignment.isSuperclass());
}
if (null != classSensorAssignment.getAnnotation()) {
return narrowByAnnotationSearch(classCache, classSensorAssignment.getAnnotation());
}
// if nothing works then we have wild-card search in name
return narrowByNameSearch(classCache, classSensorAssignment.getClassName(), classSensorAssignment.isInterf(), classSensorAssignment.isSuperclass());
}
/**
* Search by the class name defined in the {@link AbstractClassSensorAssignment} and includes
* the interface/super-class options as well.
*
* @param classCache
* {@link ClassCache} to look in.
* @param className
* Class name to search for
* @param isInterface
* if class name is related to interface
* @param isSuperClass
* if class name is related to superclass
* @return All initialized {@link ImmutableClassType} that might match given
* {@link MethodSensorAssignment}. Note that this is only narrow process, full check
* must be performed on the returned results.
*/
private Collection<? extends ImmutableClassType> narrowByNameSearch(ClassCache classCache, String className, boolean isInterface, boolean isSuperClass) {
if (isInterface) {
// if definition is for interface, load all matching interfaces
Collection<? extends ImmutableInterfaceType> interfaceTypes = classCache.getLookupService().findInterfaceTypesByPattern(className, false);
if (CollectionUtils.isEmpty(interfaceTypes)) {
return Collections.emptyList();
}
// then load initialized realizing classes from all interfaces
Collection<ImmutableClassType> results = new HashSet<>();
for (ImmutableInterfaceType interfaceType : interfaceTypes) {
collectClassesFromInterfaceAndSubInterfaces(results, interfaceType);
}
return results;
} else if (isSuperClass) {
// if definition is for superclass, load all matching classes
Collection<? extends ImmutableClassType> superClassTypes = classCache.getLookupService().findClassTypesByPattern(className, false);
if (CollectionUtils.isEmpty(superClassTypes)) {
return Collections.emptyList();
}
// then load initialized sub-classes from all super types
Collection<ImmutableClassType> results = new HashSet<>();
for (ImmutableClassType superClassType : superClassTypes) {
collectClassesFromSubClasses(results, superClassType);
}
return results;
} else {
return classCache.getLookupService().findClassTypesByPattern(className, true);
}
}
/**
* Search by the annotation defined in the assignment.
*
* @param classCache
* {@link ClassCache} to look in.
* @param annotation
* Annotation FQN
* @return All initialized {@link ImmutableClassType} that might match given annotation. Note
* that this is only narrow process, full check must be performed on the returned
* results.
*/
private Collection<? extends ImmutableClassType> narrowByAnnotationSearch(ClassCache classCache, String annotation) {
if (null == annotation) {
return Collections.emptyList();
}
Collection<? extends ImmutableAnnotationType> annotationTypes = classCache.getLookupService().findAnnotationTypesByPattern(annotation, false);
if (CollectionUtils.isEmpty(annotationTypes)) {
return Collections.emptyList();
}
// then load initialized sub-classes from all super types
Collection<ImmutableClassType> results = new HashSet<>();
for (ImmutableAnnotationType annotationType : annotationTypes) {
for (ImmutableTypeWithAnnotations typeWithAnnotations : annotationType.getImmutableAnnotatedTypes()) {
// processing if it's type
if (typeWithAnnotations.isType()) {
ImmutableType immutableType = typeWithAnnotations.castToType();
// if we have a type then take this class and all sub classes
// if it's interface then all implementing classes
if (immutableType.isClass()) {
ImmutableClassType immutableClassType = immutableType.castToClass();
if (immutableClassType.isInitialized()) {
results.add(immutableClassType);
}
collectClassesFromSubClasses(results, immutableClassType);
} else if (immutableType.isInterface()) {
ImmutableInterfaceType immutableInterfaceType = immutableType.castToInterface();
collectClassesFromInterfaceAndSubInterfaces(results, immutableInterfaceType);
}
}
// processing if it's method
if (typeWithAnnotations.isMethodType()) {
ImmutableMethodType immutableMethodType = typeWithAnnotations.castToMethodType();
ImmutableTypeWithMethods classOrInterfaceType = immutableMethodType.getImmutableClassOrInterfaceType();
if (classOrInterfaceType.isClass() && classOrInterfaceType.isInitialized()) {
results.add(classOrInterfaceType.castToClass());
}
}
}
}
return results;
}
/**
* Collects all realizing classes that implement given interface or any of its sub-interfaces
* and adds them to the given results list. This method is recursive.
*
* @param results
* List to store classes to.
* @param interfaceType
* Type to check.
*/
private void collectClassesFromInterfaceAndSubInterfaces(Collection<ImmutableClassType> results, ImmutableInterfaceType interfaceType) {
for (ImmutableClassType classType : interfaceType.getImmutableRealizingClasses()) {
if (classType.isInitialized()) {
results.add(classType);
}
collectClassesFromSubClasses(results, classType);
}
for (ImmutableInterfaceType superInterfaceType : interfaceType.getImmutableSubInterfaces()) {
collectClassesFromInterfaceAndSubInterfaces(results, superInterfaceType);
}
}
/**
* Collects all realizing classes that are sub-class of given class type or any of its
* sub-classes and adds them to the given results list. This method is recursive.
*
* @param results
* List to store classes to.
* @param classType
* Type to check.
*/
private void collectClassesFromSubClasses(Collection<ImmutableClassType> results, ImmutableClassType classType) {
for (ImmutableClassType subClassType : classType.getImmutableSubClasses()) {
if (subClassType.isInitialized()) {
results.add(subClassType);
}
collectClassesFromSubClasses(results, subClassType);
}
}
}