All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 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