io.sundr.reflect.ClassTo Maven / Gradle / Ivy
/*
* Copyright 2016 The 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
*
* http://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.sundr.reflect;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.sundr.FunctionFactory;
import io.sundr.model.AnnotationRef;
import io.sundr.model.AnnotationRefBuilder;
import io.sundr.model.AttributeKey;
import io.sundr.model.Attributeable;
import io.sundr.model.ClassRef;
import io.sundr.model.ClassRefBuilder;
import io.sundr.model.Kind;
import io.sundr.model.Method;
import io.sundr.model.MethodBuilder;
import io.sundr.model.Modifiers;
import io.sundr.model.PrimitiveRefBuilder;
import io.sundr.model.Property;
import io.sundr.model.PropertyBuilder;
import io.sundr.model.TypeDef;
import io.sundr.model.TypeDefBuilder;
import io.sundr.model.TypeParamDef;
import io.sundr.model.TypeParamDefBuilder;
import io.sundr.model.TypeParamRefBuilder;
import io.sundr.model.TypeRef;
import io.sundr.model.VoidRefBuilder;
import io.sundr.model.WildcardRefBuilder;
import io.sundr.model.repo.DefinitionRepository;
public class ClassTo {
private static final String ARGUMENT_PREFIX = "arg";
private static final Set references = new HashSet<>();
public static final Function KIND = FunctionFactory.cache(new Function() {
public Kind apply(Class item) {
if (item.isAnnotation()) {
return Kind.ANNOTATION;
} else if (item.isEnum()) {
return Kind.ENUM;
} else if (item.isInterface()) {
return Kind.INTERFACE;
} else {
return Kind.CLASS;
}
}
});
public static final Function TYPEREF = FunctionFactory.cache(new Function() {
public TypeRef apply(Type item) {
if (item == null) {
return new VoidRefBuilder().build();
} else if (item instanceof WildcardType) {
return new WildcardRefBuilder().withBounds(Arrays.asList(((WildcardType) item).getLowerBounds()).stream()
.map(t -> TYPEREF.apply(t)).collect(Collectors.toList())).build();
} else if (item instanceof TypeVariable) {
return new TypeParamRefBuilder().withName(((TypeVariable) item).getName()).build();
} else if (item instanceof GenericArrayType) {
Type target = item;
int dimensions = 0;
while (target instanceof GenericArrayType) {
target = ((GenericArrayType) target).getGenericComponentType();
dimensions++;
}
if (target instanceof Class) {
references.add((Class) target);
}
TypeRef targetRef = TYPEREF.apply(target);
return targetRef.withDimensions(dimensions + targetRef.getDimensions());
} else if (item instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) item;
Type rawType = parameterizedType.getRawType();
List arguments = new ArrayList();
for (Type arg : parameterizedType.getActualTypeArguments()) {
arguments.add(TYPEREF.apply(arg));
if (arg instanceof Class) {
references.add((Class) arg);
}
}
if (rawType instanceof Class) {
references.add((Class) rawType);
}
return new ClassRefBuilder((ClassRef) TYPEREF.apply(rawType))
.withArguments(arguments)
.build();
} else if (Object.class.equals(item)) {
return ClassRef.OBJECT;
} else if (item instanceof Class) {
Class c = (Class) item;
if (c.isArray()) {
Class target = c;
int dimensions = 0;
while (target.isArray()) {
target = ((Class) target).getComponentType();
dimensions++;
}
TypeRef targetRef = TYPEREF.apply(target);
references.add(target);
return targetRef.withDimensions(dimensions + targetRef.getDimensions());
}
if (c.isPrimitive()) {
return new PrimitiveRefBuilder().withName(c.getName()).withDimensions(0).build();
} else {
List arguments = new ArrayList();
for (TypeVariable v : c.getTypeParameters()) {
arguments.add(TYPEREF.apply(v));
}
references.add((Class) item);
String fqcn = c.getName().replaceAll(Pattern.quote("$"), ".");
return new ClassRefBuilder()
.withFullyQualifiedName(fqcn)
.withArguments(arguments)
.build();
}
}
throw new IllegalArgumentException("Can't convert type:" + item + " to a TypeRef");
}
});
public static final Function, AnnotationRef> ANNOTATIONTYPEREF = FunctionFactory
.cache(item -> {
//An annotation can't be a primitive or a void type, so its safe to cast.
ClassRef classRef = (ClassRef) TYPEREF.apply(item);
return new AnnotationRefBuilder().withClassRef(classRef).build();
});
private static final Function INTERNAL_TYPEDEF = new Function() {
public TypeDef apply(Class item) {
if (Object.class.equals(item)) {
return TypeDef.OBJECT;
}
Kind kind = KIND.apply(item);
List extendsList = new ArrayList<>();
List implementsList = new ArrayList<>();
List properties = new ArrayList<>();
List methods = new ArrayList<>();
List constructors = new ArrayList<>();
List parameters = new ArrayList<>();
if (item.getSuperclass() != null) {
extendsList.add((ClassRef) TYPEREF.apply(item.getGenericSuperclass()));
references.add(item.getSuperclass());
}
for (Class interfaceClass : item.getInterfaces()) {
references.add(interfaceClass);
}
for (Type interfaceClass : item.getGenericInterfaces()) {
TypeRef ref = TYPEREF.apply(interfaceClass);
if (ref instanceof ClassRef) {
implementsList.add((ClassRef) ref);
}
}
constructors.addAll(getConstructors(item, references));
methods.addAll(getMethods(item, references));
properties.addAll(getProperties(item, references));
for (TypeVariable typeVariable : item.getTypeParameters()) {
List bounds = new ArrayList<>();
for (Type boundType : typeVariable.getBounds()) {
TypeRef typeRef = TYPEREF.apply(boundType);
if (typeRef instanceof ClassRef) {
bounds.add((ClassRef) typeRef);
}
}
parameters.add(new TypeParamDefBuilder()
.withName(typeVariable.getName())
.withBounds(bounds)
.build());
}
String outerFQCN = item.getDeclaringClass() != null ? item.getDeclaringClass().getName() : null;
TypeDef result = DefinitionRepository.getRepository().register(new TypeDefBuilder()
.withKind(kind)
.withOuterTypeName(outerFQCN)
.withName(item.getSimpleName())
.withPackageName(item.getPackage() != null ? item.getPackage().getName() : null)
.withModifiers(Modifiers.from(item.getModifiers()))
.withParameters(parameters)
.withConstructors(constructors)
.withMethods(methods)
.withProperties(properties)
.withExtendsList(extendsList)
.withImplementsList(implementsList)
.build());
Set copy = new HashSet<>(references);
copy.stream()
.peek(c -> references.remove(c))
.filter(c -> !c.equals(item))
.filter(c -> !c.getName().startsWith("sun.") && !c.getName().toString().startsWith("com.sun."))
.forEach(c -> {
String referenceFQCN = c.getName().replaceAll(Pattern.quote("$"), ".");
DefinitionRepository.getRepository().registerIfAbsent(referenceFQCN, () -> apply(c));
});
return result;
}
};
public static final Function TYPEDEF = INTERNAL_TYPEDEF;
// public static final Function TYPEDEF = FunctionFactory.cache(INTERNAL_TYPEDEF);
// .withFallback(INTERNAL_SHALLOW_TYPEDEF).withMaximumRecursionLevel(10).withMaximumNestingDepth(10);
private static Function TYPEPARAMDEF = FunctionFactory.cache(new Function() {
public TypeParamDef apply(Type item) {
if (item instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) item;
String name = typeVariable.getName();
List bounds = new ArrayList();
for (Type b : typeVariable.getBounds()) {
if (b instanceof Class) {
Class c = (Class) b;
bounds.add((ClassRef) TYPEREF.apply(c));
}
}
return new TypeParamDefBuilder().withName(name).withBounds(bounds).build();
}
return null;
}
});
private static Set getProperties(Class item, Set references) {
Set properties = new HashSet();
for (Field field : item.getDeclaredFields()) {
List annotationRefs = new ArrayList();
processAnnotatedElement(field, annotationRefs);
if (field.getGenericType() instanceof Class) {
references.add((Class) field.getGenericType());
}
// If property contains generic bounds, we need to process them too.
if (field.getGenericType() instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) field.getGenericType();
references.addAll(Stream.of(p.getActualTypeArguments()).filter(t -> t instanceof Class)
.map(t -> (Class) t)
.filter(c -> !item.equals(c))
.collect(Collectors.toList()));
}
properties.add(new PropertyBuilder()
.withName(field.getName())
.withModifiers(Modifiers.from(field.getModifiers()))
.withEnumConstant(field.isEnumConstant())
.withSynthetic(field.isSynthetic())
.withAnnotations(annotationRefs)
.withTypeRef(TYPEREF.apply(field.getGenericType()))
.build());
}
return properties;
}
private static void processAnnotatedElement(AnnotatedElement field, List annotationRefs) {
for (Annotation annotation : field.getDeclaredAnnotations()) {
final Class extends Annotation> annotationType = annotation.annotationType();
AnnotationRef annotationRef = ANNOTATIONTYPEREF.apply(annotationType);
Map parameters = new HashMap<>();
for (java.lang.reflect.Method method : annotationType.getDeclaredMethods()) {
final String name = method.getName();
try {
final Object value = method.invoke(annotation, (Object[]) null);
parameters.put(name, value);
} catch (IllegalAccessException | InvocationTargetException e) {
//Let's not pollute output with internal jdk stuff.
if (!annotationType.getName().startsWith("jdk.")) {
System.out.printf("Couldn't retrieve '%s' parameter value for %s%n", name, annotationType.getName());
}
}
}
annotationRef = new AnnotationRefBuilder(annotationRef).withParameters(parameters).build();
annotationRefs.add(annotationRef);
}
}
private static Set getConstructors(Class item, Set references) {
Set constructors = new HashSet();
for (java.lang.reflect.Constructor constructor : item.getDeclaredConstructors()) {
List annotationRefs = new ArrayList();
List exceptionRefs = new ArrayList<>();
List arguments = new ArrayList();
List parameters = new ArrayList();
processMethod(references, constructor, annotationRefs, exceptionRefs, arguments, parameters);
constructors.add(new MethodBuilder()
.withName(constructor.getName())
.withModifiers(Modifiers.from(constructor.getModifiers()))
.withArguments(arguments)
.withParameters(parameters)
.withAnnotations(annotationRefs)
.withExceptions(exceptionRefs)
.build());
}
return constructors;
}
private static Set getMethods(Class item, Set references) {
Set methods = new HashSet();
for (java.lang.reflect.Method method : item.getDeclaredMethods()) {
List annotationRefs = new ArrayList<>();
List exceptionRefs = new ArrayList<>();
List arguments = new ArrayList();
List parameters = new ArrayList();
processMethod(references, method, annotationRefs, exceptionRefs, arguments, parameters);
Map attributes = new HashMap<>();
if (method.getDefaultValue() != null) {
attributes.put(Attributeable.DEFAULT_VALUE, String.valueOf(method.getDefaultValue()));
}
methods.add(new MethodBuilder()
.withName(method.getName())
.withDefaultMethod(method.isDefault())
.withModifiers(Modifiers.from(method.getModifiers()))
.withReturnType(TYPEREF.apply(method.getReturnType()))
.withArguments(arguments)
.withParameters(parameters)
.withExceptions(exceptionRefs)
.withAnnotations(annotationRefs)
.withAttributes(attributes)
.build());
}
return methods;
}
private static void processMethod(Set references, java.lang.reflect.Executable method,
List annotationRefs, List exceptionRefs, List arguments,
List parameters) {
processAnnotatedElement(method, annotationRefs);
for (Class exceptionType : method.getExceptionTypes()) {
exceptionRefs.add((ClassRef) TYPEREF.apply(exceptionType));
}
for (int i = 1; i <= method.getGenericParameterTypes().length; i++) {
Type argumentType = method.getGenericParameterTypes()[i - 1];
arguments.add(new PropertyBuilder()
.withName(ARGUMENT_PREFIX + i)
.withTypeRef(TYPEREF.apply(argumentType))
.build());
if (argumentType instanceof Class) {
references.add((Class) argumentType);
}
}
for (Type type : method.getGenericParameterTypes()) {
TypeParamDef typeParamDef = TYPEPARAMDEF.apply(type);
if (typeParamDef != null) {
parameters.add(typeParamDef);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy