/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.runtime.module.extension.internal.util;
import static java.lang.String.format;
import static java.lang.reflect.Modifier.isPublic;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang.ArrayUtils.isEmpty;
import static org.mule.metadata.api.builder.BaseTypeBuilder.create;
import static org.mule.metadata.api.model.MetadataFormat.JAVA;
import static org.mule.metadata.api.utils.MetadataTypeUtils.isEnum;
import static org.mule.metadata.api.utils.MetadataTypeUtils.isObjectType;
import static org.mule.metadata.java.api.utils.JavaTypeUtils.getType;
import static org.mule.runtime.api.meta.ExpressionSupport.SUPPORTED;
import static org.mule.runtime.api.util.Preconditions.checkArgument;
import static org.reflections.ReflectionUtils.getAllFields;
import static org.reflections.ReflectionUtils.getAllSuperTypes;
import static org.reflections.ReflectionUtils.withName;
import static org.springframework.core.ResolvableType.forMethodReturnType;
import static org.springframework.core.ResolvableType.forType;
import org.mule.metadata.api.ClassTypeLoader;
import org.mule.metadata.api.builder.BaseTypeBuilder;
import org.mule.metadata.api.model.AnyType;
import org.mule.metadata.api.model.ArrayType;
import org.mule.metadata.api.model.MetadataType;
import org.mule.metadata.api.model.ObjectFieldType;
import org.mule.metadata.api.model.ObjectType;
import org.mule.metadata.api.model.StringType;
import org.mule.metadata.api.model.VoidType;
import org.mule.metadata.api.visitor.MetadataTypeVisitor;
import org.mule.metadata.java.api.annotation.ClassInformationAnnotation;
import org.mule.metadata.java.api.utils.JavaTypeUtils;
import org.mule.metadata.message.MessageMetadataTypeBuilder;
import org.mule.runtime.api.lifecycle.Disposable;
import org.mule.runtime.api.lifecycle.Initialisable;
import org.mule.runtime.api.lifecycle.Startable;
import org.mule.runtime.api.lifecycle.Stoppable;
import org.mule.runtime.api.message.Message;
import org.mule.runtime.api.meta.ExpressionSupport;
import org.mule.runtime.api.meta.model.ComponentModel;
import org.mule.runtime.api.meta.model.EnrichableModel;
import org.mule.runtime.api.meta.model.ExtensionModel;
import org.mule.runtime.api.meta.model.ModelProperty;
import org.mule.runtime.api.meta.model.declaration.fluent.BaseDeclaration;
import org.mule.runtime.api.meta.model.declaration.fluent.ParameterDeclaration;
import org.mule.runtime.api.meta.model.parameter.ParameterGroupModel;
import org.mule.runtime.api.meta.model.parameter.ParameterModel;
import org.mule.runtime.api.meta.model.parameter.ParameterizedModel;
import org.mule.runtime.api.meta.model.util.ExtensionWalker;
import org.mule.runtime.api.metadata.DataType;
import org.mule.runtime.api.metadata.TypedValue;
import org.mule.runtime.api.util.Reference;
import org.mule.runtime.core.util.ClassUtils;
import org.mule.runtime.core.util.CollectionUtils;
import org.mule.runtime.core.util.collection.ImmutableListCollector;
import org.mule.runtime.extension.api.annotation.Alias;
import org.mule.runtime.extension.api.annotation.Expression;
import org.mule.runtime.extension.api.annotation.Ignore;
import org.mule.runtime.extension.api.annotation.metadata.MetadataKeyId;
import org.mule.runtime.extension.api.annotation.param.Parameter;
import org.mule.runtime.extension.api.annotation.param.ParameterGroup;
import org.mule.runtime.extension.api.declaration.type.annotation.LiteralTypeAnnotation;
import org.mule.runtime.extension.api.declaration.type.annotation.ParameterResolverTypeAnnotation;
import org.mule.runtime.extension.api.declaration.type.annotation.TypedValueTypeAnnotation;
import org.mule.runtime.extension.api.exception.IllegalModelDefinitionException;
import org.mule.runtime.extension.api.runtime.operation.Result;
import org.mule.runtime.extension.api.runtime.parameter.Literal;
import org.mule.runtime.extension.api.runtime.parameter.ParameterResolver;
import org.mule.runtime.extension.api.runtime.source.Source;
import org.mule.runtime.extension.api.runtime.streaming.PagingProvider;
import org.mule.runtime.extension.internal.property.LiteralModelProperty;
import org.mule.runtime.module.extension.internal.loader.java.MuleExtensionAnnotationParser;
import org.mule.runtime.module.extension.internal.loader.java.property.DeclaringMemberModelProperty;
import org.mule.runtime.module.extension.internal.loader.java.property.ImplementingParameterModelProperty;
import org.mule.runtime.module.extension.internal.loader.java.property.ParameterResolverTypeModelProperty;
import org.mule.runtime.module.extension.internal.loader.java.property.TypedValueTypeModelProperty;
import com.google.common.collect.ImmutableList;
import org.springframework.core.ResolvableType;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
/**
* Set of utility operations to get insights about objects and their components
*
* @since 3.7.0
*/
public final class IntrospectionUtils {
private IntrospectionUtils() {}
/**
* Returns a {@link MetadataType} representing the given {@link Class} type.
*
* @param type the {@link Class} being introspected
* @param typeLoader a {@link ClassTypeLoader} used to create the {@link MetadataType}
* @return a {@link MetadataType}
*/
public static MetadataType getMetadataType(Class<?> type, ClassTypeLoader typeLoader) {
return typeLoader.load(ResolvableType.forClass(type).getType());
}
/**
* Transforms a {@link MetadataType} and generates the correspondent {@link DataType}
*
* @param metadataType to introspect a create a {@link DataType} from it.
* @return a {@link DataType} based on the given {@link MetadataType}
*/
public static DataType toDataType(MetadataType metadataType) {
Class<?> type = getType(metadataType);
Reference<DataType> dataType = new Reference<>();
metadataType.accept(new MetadataTypeVisitor() {
@Override
protected void defaultVisit(MetadataType metadataType) {
dataType.set(DataType.fromType(type));
}
@Override
public void visitArrayType(ArrayType arrayType) {
Class itemClass = getType(arrayType.getType());
if (Collection.class.isAssignableFrom(type)) {
dataType.set(DataType.builder()
.collectionType((Class<? extends Collection>) type)
.itemType(itemClass)
.build());
} else if (Iterator.class.isAssignableFrom(type)) {
dataType.set(DataType.builder()
.streamType((Class<? extends Iterator>) type)
.itemType(itemClass)
.build());
} else {
defaultVisit(arrayType);
}
}
@Override
public void visitObject(ObjectType objectType) {
if (Map.class.isAssignableFrom(type)) {
dataType.set(DataType.builder().mapType((Class<? extends Map>) type)
.keyType(String.class)
.valueType(objectType.getOpenRestriction()
.map(JavaTypeUtils::getType)
.orElse(Object.class))
.build());
} else {
defaultVisit(objectType);
}
}
});
return dataType.get();
}
/**
* Returns a {@link MetadataType} representing the given {@link Method}'s return type. If the {@code method} returns an
* {@link Result}, then it returns the type of the first generic. If the {@link Result} type is being used in its raw
* form, then an {@link AnyType} will be returned.
* <p>
* If the {@code method} returns a collection of {@link Result} instances, then it will
* return an {@link ArrayType} which inner value represent a {@link Message} which payload
* and attributes matches the types of the Result generics.
*
* @param method the {@link Method} being introspected
* @param typeLoader a {@link ClassTypeLoader} used to create the {@link MetadataType}
* @return a {@link MetadataType}
* @throws IllegalArgumentException is method is {@code null}
*/
public static MetadataType getMethodReturnType(Method method, ClassTypeLoader typeLoader) {
return getReturnType(getMethodType(method), typeLoader);
}
/**
* Returns the {@link MetadataType} for a source's output.
* <p>
* If the {@code type} is a collection of {@link Result} instances, then it will
* return an {@link ArrayType} which inner value represent a {@link Message} which payload
* and attributes matches the types of the Result generics.
*
* @param returnType the source output type
* @param typeLoader a {@link ClassTypeLoader} used to create the {@link MetadataType}
* @return a {@link MetadataType}
*/
public static MetadataType getSourceReturnType(Type returnType, ClassTypeLoader typeLoader) {
return getReturnType(forType(returnType), typeLoader);
}
private static MetadataType getReturnType(ResolvableType returnType, ClassTypeLoader typeLoader) {
Type type = returnType.getType();
final Class<?> rawClass = returnType.getRawClass();
if (rawClass.equals(Result.class)) {
ResolvableType genericType = returnType.getGenerics()[0];
if (genericType.getRawClass() != null) {
type = genericType.getType();
} else {
type = null;
}
}
if (isPagingProvider(returnType)) {
ResolvableType itemType = returnType.getGenerics()[1];
if (Result.class.equals(itemType.getRawClass())) {
return returnListOfMessagesType(returnType, typeLoader, itemType);
} else {
return typeBuilder().arrayType().id(rawClass.getName()).of(typeLoader.load(itemType.getType())).build();
}
}
if (ParameterResolver.class.isAssignableFrom(rawClass) ||
TypedValue.class.isAssignableFrom(rawClass) ||
Literal.class.isAssignableFrom(rawClass)) {
type = returnType.getGenerics()[0].getType();
}
if (isCollection(returnType)) {
ResolvableType itemType = returnType.getGenerics()[0];
if (Result.class.equals(itemType.getRawClass())) {
return returnListOfMessagesType(returnType, typeLoader, itemType);
}
}
return type != null ? typeLoader.load(type) : typeBuilder().anyType().build();
}
private static MetadataType returnListOfMessagesType(ResolvableType returnType, ClassTypeLoader typeLoader,
ResolvableType itemType) {
ResolvableType genericType = itemType.getGenerics()[0];
MetadataType outputType = genericType.getRawClass() != null
? typeLoader.load(genericType.getType())
: typeBuilder().anyType().build();
genericType = itemType.getGenerics()[1];
MetadataType attributesType = genericType.getRawClass() != null
? typeLoader.load(genericType.getType())
: typeBuilder().voidType().build();
return typeBuilder().arrayType().id(returnType.getRawClass().getName())
.of(new MessageMetadataTypeBuilder().payload(outputType).attributes(attributesType).build())
.build();
}
private static boolean isCollection(ResolvableType type) {
return Collection.class.isAssignableFrom(type.getRawClass());
}
/**
* Returns a {@link MetadataType} representing the {@link Result#getAttributes()} that will be set after executing the given
* {@code method}.
* <p>
* If the {@code method} returns a {@link Result}, then it returns the type of the {@code Attributes} generic. In any other case
* (including raw uses of {@link Result}) it will return a {@link VoidType}
* <p>
* If the {@code method} returns a collection or a {@link PagingProvider} of {@link Result}, then this will return {@link VoidType} since the messages in the
* main output already contain an attributes for each item.
*
* @param method the {@link Method} being introspected
* @param typeLoader a {@link ClassTypeLoader} used to create the {@link MetadataType}
* @return a {@link MetadataType}
* @throws IllegalArgumentException is method is {@code null}
*/
public static MetadataType getMethodReturnAttributesType(Method method, ClassTypeLoader typeLoader) {
ResolvableType outputType = getMethodType(method);
Type type = null;
if (Result.class.equals(outputType.getRawClass())) {
ResolvableType genericType = outputType.getGenerics()[1];
if (genericType.getRawClass() != null) {
type = genericType.getType();
}
}
if (isPagingProvider(outputType)) {
ResolvableType itemType = outputType.getGenerics()[1];
if (Result.class.equals(itemType.getRawClass())) {
type = null;
}
}
if (isCollection(outputType)) {
ResolvableType itemType = outputType.getGenerics()[0];
if (Result.class.equals(itemType.getRawClass())) {
type = null;
}
}
return type != null ? typeLoader.load(type) : typeBuilder().voidType().build();
}
public static List<MetadataType> getGenerics(Type type, ClassTypeLoader typeLoader) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] generics = parameterizedType.getActualTypeArguments();
return Stream.of(generics).map(typeLoader::load).collect(toList());
}
return new LinkedList<>();
}
public static ResolvableType getMethodType(Method method) {
checkArgument(method != null, "Can't introspect a null method");
return forMethodReturnType(method);
}
public static ResolvableType unwrapGenericFromClass(Class<?> clazz, ResolvableType type, int genericIndex) {
if (!isEmpty(type.getGenerics())) {
ResolvableType genericType = type.getGenerics()[genericIndex];
if (genericType.getRawClass() != null) {
type = genericType;
}
} else {
if (clazz.isAssignableFrom(type.getRawClass().getSuperclass())) {
return unwrapGenericFromClass(clazz, type.getSuperType(), genericIndex);
} else {
ResolvableType interfaceType = stream(type.getInterfaces())
.filter(i -> clazz.isAssignableFrom(i.getRawClass()))
.findFirst()
.orElse(forType(Object.class));
return unwrapGenericFromClass(clazz, interfaceType, genericIndex);
}
}
return type;
}
/**
* Determines if the given {@code type} implements any of the lifecycle
* annotations
*
* @param type the class to evaluate
* @return whether it implements lifecycle or not
*/
public static boolean isLifecycle(Class<?> type) {
return Initialisable.class.isAssignableFrom(type)
|| Startable.class.isAssignableFrom(type)
|| Stoppable.class.isAssignableFrom(type)
|| Disposable.class.isAssignableFrom(type);
}
private static boolean isPagingProvider(ResolvableType type) {
return PagingProvider.class.isAssignableFrom(type.getRawClass());
}
private static BaseTypeBuilder typeBuilder() {
return create(JAVA);
}
/**
* Returns an array of {@link MetadataType} representing each of the given {@link Method}'s argument types.
*
* @param method a not {@code null} {@link Method}
* @param typeLoader a {@link ClassTypeLoader} to be used to create the returned {@link MetadataType}s
* @return an array of {@link MetadataType} matching the method's arguments. If the method doesn't take any, then the array will
* be empty
* @throws IllegalArgumentException is method is {@code null}
*/
public static MetadataType[] getMethodArgumentTypes(Method method, ClassTypeLoader typeLoader) {
checkArgument(method != null, "Can't introspect a null method");
Class<?>[] parameters = method.getParameterTypes();
if (isEmpty(parameters)) {
return new MetadataType[] {};
}
MetadataType[] types = new MetadataType[parameters.length];
for (int i = 0; i < parameters.length; i++) {
ResolvableType type = ResolvableType.forMethodParameter(method, i);
types[i] = typeLoader.load(type.getType());
}
return types;
}
/**
* Returns a {@link MetadataType} describing the given {@link Field}'s type
*
* @param field a not {@code null} {@link Field}
* @param typeLoader a {@link ClassTypeLoader} used to create the {@link MetadataType}
* @return a {@link MetadataType} matching the field's type
* @throws IllegalArgumentException if field is {@code null}
*/
public static MetadataType getFieldMetadataType(Field field, ClassTypeLoader typeLoader) {
checkArgument(field != null, "Can't introspect a null field");
return typeLoader.load(ResolvableType.forField(field).getType());
}
public static Optional<Field> getFieldByNameOrAlias(Class<?> clazz, String nameOrAlias) {
Optional<Field> field = getField(clazz, nameOrAlias);
if (!field.isPresent()) {
field = getAllFields(clazz, f -> getAlias(f).equals(nameOrAlias)).stream().findFirst();
}
return field;
}
public static Optional<Field> getField(Class<?> clazz, ParameterModel parameterModel) {
return getField(clazz, getMemberName(parameterModel, parameterModel.getName()));
}
public static Optional<Field> getField(Class<?> clazz, ParameterDeclaration parameterDeclaration) {
return getField(clazz, MuleExtensionAnnotationParser.getMemberName(parameterDeclaration, parameterDeclaration.getName()));
}
public static Optional<Field> getField(Class<?> clazz, String name) {
Collection<Field> candidates = getAllFields(clazz, withName(name));
return CollectionUtils.isEmpty(candidates) ? Optional.empty() : Optional.of(candidates.iterator().next());
}
/**
* Resolves and returns the field value of an object instance
*
* @param object The object where grab the field value
* @param fieldName The name of the field to obtain the value
* @return The value of the field with the given fieldName and object instance
* @throws IllegalAccessException if is unavailable to access to the field
* @throws NoSuchFieldException if the field doesn't exist in the given object instance
*/
public static Object getFieldValue(Object object, String fieldName) throws IllegalAccessException, NoSuchFieldException {
final Optional<Field> fieldOptional = getField(object.getClass(), fieldName);
if (fieldOptional.isPresent()) {
final Field field = fieldOptional.get();
field.setAccessible(true);
return field.get(object);
} else {
throw new NoSuchFieldException();
}
}
public static String getMemberName(EnrichableModel enrichableModel, String defaultName) {
return enrichableModel.getModelProperty(DeclaringMemberModelProperty.class).map(p -> p.getDeclaringField().getName())
.orElse(defaultName);
}
public static boolean hasDefaultConstructor(Class<?> clazz) {
return ClassUtils.getConstructor(clazz, new Class[] {}) != null;
}
public static List<Class<?>> getInterfaceGenerics(final Class<?> type, final Class<?> implementedInterface) {
ResolvableType interfaceType = null;
Class<?> searchClass = type;
while (!Object.class.equals(searchClass)) {
for (ResolvableType iType : ResolvableType.forClass(searchClass).getInterfaces()) {
if (implementedInterface.isAssignableFrom(iType.getRawClass())) {
interfaceType = iType;
break;
}
}
if (interfaceType != null) {
break;
} else {
searchClass = searchClass.getSuperclass();
}
}
if (interfaceType == null) {
throw new IllegalArgumentException(format("Class '%s' does not implement the '%s' interface", type.getName(),
implementedInterface.getName()));
}
List<? super Class<?>> generics = toRawClasses(interfaceType.getGenerics());
if (generics.stream().anyMatch(c -> c == null)) {
return findGenericsInSuperHierarchy(type);
}
return (List<Class<?>>) generics;
}
public static List<Class<?>> findGenericsInSuperHierarchy(final Class<?> type) {
if (Object.class.equals(type)) {
return ImmutableList.of();
}
Class<?> superClass = type.getSuperclass();
List<Type> generics = getSuperClassGenerics(type, superClass);
if (CollectionUtils.isEmpty(generics) && !Object.class.equals(superClass)) {
return findGenericsInSuperHierarchy(superClass);
}
return (List) generics;
}
private static List<Class<?>> toRawClasses(ResolvableType... types) {
return stream(types).map(ResolvableType::getRawClass).collect(toList());
}
public static List<Type> getSuperClassGenerics(Class<?> type, Class<?> superClass) {
Class<?> searchClass = type;
checkArgument(searchClass.getSuperclass().equals(superClass), format(
"Class '%s' does not extend the '%s' class",
type.getName(), superClass.getName()));
while (!Object.class.equals(searchClass)) {
if (searchClass.getSuperclass().equals(superClass)) {
Type superType = searchClass.getGenericSuperclass();
if (superType instanceof ParameterizedType) {
return stream(((ParameterizedType) superType).getActualTypeArguments()).collect(toList());
}
}
searchClass = searchClass.getSuperclass();
}
return new LinkedList<>();
}
public static void checkInstantiable(Class<?> declaringClass) {
checkInstantiable(declaringClass, true);
}
public static void checkInstantiable(Class<?> declaringClass, boolean requireDefaultConstructor) {
if (!isInstantiable(declaringClass, requireDefaultConstructor)) {
throw new IllegalArgumentException(format("Class %s cannot be instantiated.", declaringClass));
}
}
public static boolean isInstantiable(MetadataType type) {
return isInstantiable(getType(type));
}
public static boolean isInstantiable(Class<?> declaringClass) {
return isInstantiable(declaringClass, true);
}
public static boolean isInstantiable(Class<?> declaringClass, boolean requireDefaultConstructor) {
return declaringClass != null && (!requireDefaultConstructor || hasDefaultConstructor(declaringClass))
&& !declaringClass.isInterface() && !Modifier.isAbstract(declaringClass.getModifiers());
}
/**
* Determines if the given {@code type} is assignable from any of the {@code matchingTypes}
*
* @param type a {@link Class}
* @param matchingTypes a collection of {@link Class classes} to test against
* @return whether the type is assignable or not
*/
public static boolean assignableFromAny(Class<?> type, Collection<Class<?>> matchingTypes) {
return matchingTypes.stream().anyMatch(t -> t.isAssignableFrom(type));
}
public static boolean isRequired(AccessibleObject object) {
return object.getAnnotation(org.mule.runtime.extension.api.annotation.param.Optional.class) == null;
}
public static boolean isRequired(ParameterModel parameterModel, boolean forceOptional) {
return !forceOptional && parameterModel.isRequired();
}
public static boolean isVoid(Method method) {
return isVoid(method.getReturnType());
}
public static boolean isVoid(ComponentModel componentModel) {
return componentModel.getOutput().getType() instanceof VoidType;
}
private static boolean isVoid(Class<?> type) {
return type.equals(void.class) || type.equals(Void.class);
}
public static Collection<Method> getOperationMethods(Class<?> declaringClass) {
return getMethodsStream(declaringClass)
.filter(method -> !method.isAnnotationPresent(Ignore.class) && !isLifecycleMethod(method))
.collect(toCollection(LinkedHashSet::new));
}
private static boolean isLifecycleMethod(Method method) {
return isLifecycleMethod(method, Initialisable.class, "initialise")
|| isLifecycleMethod(method, Startable.class, "start")
|| isLifecycleMethod(method, Stoppable.class, "stop")
|| isLifecycleMethod(method, Disposable.class, "dispose");
}
private static boolean isLifecycleMethod(Method method, Class<?> lifecycleClass, String lifecycleMethodName) {
return lifecycleClass.isAssignableFrom(method.getDeclaringClass()) && method.getName().equals(lifecycleMethodName);
}
/**
* Returns all the methods in the {@code declaringClass} which are annotated with {@code annotationType}
*
* @param declaringClass the type to introspect
* @param annotationType the annotation you're looking for
* @return a {@link Collection} of {@link Method}s
*/
public static Collection<Method> getMethodsAnnotatedWith(Class<?> declaringClass, Class<? extends Annotation> annotationType) {
return getMethodsStream(declaringClass)
.filter(method -> method.getAnnotation(annotationType) != null)
.collect(toCollection(LinkedHashSet::new));
}
private static Stream<Method> getMethodsStream(Class<?> declaringClass) {
return getAllSuperTypes(declaringClass).stream()
.filter(type -> !type.isInterface())
.flatMap(type -> Stream.of(type.getDeclaredMethods()))
.filter(method -> isPublic(method.getModifiers()));
}
public static List<Field> getAnnotatedFields(Class<?> clazz, Class<? extends Annotation> annotationType) {
return getDescendingHierarchy(clazz).stream().flatMap(type -> stream(type.getDeclaredFields()))
.filter(field -> field.getAnnotation(annotationType) != null).collect(new ImmutableListCollector<>());
}
public static List<Field> getFields(Class<?> clazz) {
return getDescendingHierarchy(clazz).stream().flatMap(type -> stream(type.getDeclaredFields()))
.collect(new ImmutableListCollector<>());
}
/**
* Returns the {@link Alias} name of the given {@code element}. If the element doesn't have an alias, then the default name is
* return
*
* @param element an annotated member
* @param <T> the generic type of the element
* @return an alias name
*/
public static <T extends AnnotatedElement & Member> String getAlias(T element) {
Alias alias = element.getAnnotation(Alias.class);
return alias != null ? alias.value() : element.getName();
}
private static List<Class<?>> getDescendingHierarchy(Class<?> type) {
List<Class<?>> types = new LinkedList<>();
types.add(type);
for (type = type.getSuperclass(); type != null && !Object.class.equals(type); type = type.getSuperclass()) {
types.add(0, type);
}
return ImmutableList.copyOf(types);
}
public static Collection<Field> getExposedFields(Class<?> extensionType) {
Collection<Field> allFields = getAnnotatedFields(extensionType, Parameter.class);
if (!allFields.isEmpty()) {
return allFields;
}
return getFieldsWithGetters(extensionType);
}
public static Set<Field> getFieldsWithGetters(Class<?> extensionType) {
return getPropertyDescriptors(extensionType).stream().filter(p -> p.getReadMethod() != null)
.map(p -> getField(extensionType, p.getName())).filter(Optional::isPresent).map(Optional::get)
.collect(toSet());
}
private static List<PropertyDescriptor> getPropertyDescriptors(Class<?> extensionType) {
try {
PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(extensionType).getPropertyDescriptors();
return Arrays.asList(propertyDescriptors);
} catch (IntrospectionException e) {
throw new IllegalModelDefinitionException("Could not introspect POJO: " + extensionType.getName(), e);
}
}
public static ExpressionSupport getExpressionSupport(AnnotatedElement object) {
return getExpressionSupport(object.getAnnotation(Expression.class));
}
public static ExpressionSupport getExpressionSupport(Expression expressionAnnotation) {
return expressionAnnotation != null ? expressionAnnotation.value() : SUPPORTED;
}
public static String getSourceName(Class<? extends Source> sourceType) {
Alias alias = sourceType.getAnnotation(Alias.class);
if (alias != null) {
return alias.value();
}
return sourceType.getSimpleName();
}
/**
* Looks for the annotation in the given class. If the annotation is not found, it keeps looking recursively for it in the
* superClass until it finds it or there is no superClass to analyze.
*/
public static <T extends Annotation> T getAnnotation(Class<?> annotatedClass, Class<T> annotationClass) {
T annotation = annotatedClass.getAnnotation(annotationClass);
Class<?> superClass = annotatedClass.getSuperclass();
while (annotation == null && superClass != null && !superClass.equals(Object.class)) {
annotation = superClass.getAnnotation(annotationClass);
superClass = superClass.getSuperclass();
}
return annotation;
}
/**
* Traverses through all the {@link ParameterModel}s of the {@code extensionModel} and returns the {@link Class classes} that
* are modeled by each parameter's {@link ParameterModel#getType()}.
* <p>
* This includes every single {@link ParameterModel} in the model, including configs, providers, operations, etc.
*
* @param extensionModel a {@link ExtensionModel}
* @return a non {@code null} {@link Set}
*/
public static Set<Class<?>> getParameterClasses(ExtensionModel extensionModel, ClassLoader extensionClassLoader) {
Set<Class<?>> parameterClasses = new HashSet<>();
new ExtensionWalker() {
@Override
public void onParameter(ParameterizedModel owner, ParameterGroupModel groupModel, ParameterModel model) {
parameterClasses.addAll(collectRelativeClasses(model.getType(), extensionClassLoader));
}
}.walk(extensionModel);
return parameterClasses;
}
/**
* Given a {@link MetadataType} it adds all the {@link Class} that are related from that type. This includes generics of an
* {@link ArrayType}, open restriction of an {@link ObjectType} as well as its fields.
*
* @param type {@link MetadataType} to inspect
* @param extensionClassLoader extension class loader
* @return {@link Set<Class<?>>} with the classes reachable from the {@code type}
*/
public static Set<Class<?>> collectRelativeClasses(MetadataType type, ClassLoader extensionClassLoader) {
Set<Class<?>> relativeClasses = new HashSet<>();
type.accept(new MetadataTypeVisitor() {
@Override
public void visitArrayType(ArrayType arrayType) {
arrayType.getType().accept(this);
}
@Override
public void visitObjectField(ObjectFieldType objectFieldType) {
objectFieldType.getValue().accept(this);
}
@Override
public void visitObject(ObjectType objectType) {
if (objectType.getMetadataFormat() != JAVA) {
return;
}
if (!relativeClasses.contains(getType(objectType))) {
Optional<ClassInformationAnnotation> classInformation = objectType.getAnnotation(ClassInformationAnnotation.class);
if (classInformation.isPresent()) {
classInformation.get().getGenericTypes()
.forEach(generic -> relativeClasses.add(loadClass(generic, extensionClassLoader)));
}
relativeClasses.add(getType(objectType));
objectType.getFields().stream().forEach(objectFieldType -> objectFieldType.accept(this));
objectType.getOpenRestriction().ifPresent(t -> t.accept(this));
}
}
@Override
public void visitString(StringType stringType) {
if (stringType.getMetadataFormat() == JAVA && isEnum(stringType)) {
relativeClasses.add(getType(stringType));
}
}
});
return relativeClasses;
}
private static Class loadClass(String name, ClassLoader extensionClassloader) {
try {
return ClassUtils.loadClass(name, extensionClassloader);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Given a {@link Set} of Annotation classes and a {@link MetadataType} that describes a component parameter, indicates if the
* parameter is considered as a multilevel {@link MetadataKeyId}
*
* @param annotations of the parameter
* @param parameterType of the parameter
* @return a boolean indicating if the Parameter is considered as a multilevel {@link MetadataKeyId}
*/
public static boolean isMultiLevelMetadataKeyId(Set<Class<? extends Annotation>> annotations, MetadataType parameterType) {
return annotations.contains(MetadataKeyId.class) && isObjectType(parameterType);
}
/**
* Given a {@link Set} of annotation classes and a {@link MetadataType} of a component parameter, indicates if the parameter is
* a parameter container.
* <p>
* To be a parameter container means that the parameter is a {@link ParameterGroup} or a multilevel {@link MetadataKeyId}.
*
* @param annotations of the component parameter
* @param parameterType of the component parameter
* @return a boolean indicating if the parameter is considered as a parameter container
*/
public static boolean isParameterContainer(Set<Class<? extends Annotation>> annotations, MetadataType parameterType) {
return (annotations.contains(ParameterGroup.class) || isMultiLevelMetadataKeyId(annotations, parameterType));
}
public static java.util.Optional<AnnotatedElement> getAnnotatedElement(BaseDeclaration<?> declaration) {
final java.util.Optional<DeclaringMemberModelProperty> declaringMember =
declaration.getModelProperty(DeclaringMemberModelProperty.class);
final java.util.Optional<ImplementingParameterModelProperty> implementingParameter =
declaration.getModelProperty(ImplementingParameterModelProperty.class);
AnnotatedElement annotatedElement = null;
if (declaringMember.isPresent()) {
annotatedElement = declaringMember.get().getDeclaringField();
}
if (implementingParameter.isPresent()) {
annotatedElement = implementingParameter.get().getParameter();
}
return java.util.Optional.ofNullable(annotatedElement);
}
public static String getContainerName(AnnotatedElement container) {
if (container instanceof Field) {
return ((Field) container).getName();
} else if (container instanceof java.lang.reflect.Parameter) {
return ((java.lang.reflect.Parameter) container).getName();
} else {
throw new IllegalArgumentException("Unknown container type");
}
}
public static boolean isParameterResolver(Set<ModelProperty> modelProperties) {
return modelProperties.stream().anyMatch(modelProperty -> modelProperty instanceof ParameterResolverTypeModelProperty);
}
public static boolean isParameterResolver(MetadataType metadataType) {
return metadataType.getAnnotation(ParameterResolverTypeAnnotation.class).isPresent();
}
public static boolean isLiteral(Set<ModelProperty> modelProperties) {
return modelProperties.stream().anyMatch(modelProperty -> modelProperty instanceof LiteralModelProperty);
}
public static boolean isLiteral(MetadataType metadataType) {
return metadataType.getAnnotation(LiteralTypeAnnotation.class).isPresent();
}
public static boolean isTypedValue(Set<ModelProperty> modelProperties) {
return modelProperties.stream().anyMatch(modelProperty -> modelProperty instanceof TypedValueTypeModelProperty);
}
public static boolean isTypedValue(MetadataType metadataType) {
return metadataType.getAnnotation(TypedValueTypeAnnotation.class).isPresent();
}
}