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

org.swat.spring.SingletonVerifier Maven / Gradle / Ivy

/*
 * Copyright © 2018 Swatantra Agrawal. All rights reserved.
 */

package org.swat.spring;

import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;


/**
 * The type Singleton verifier.
 */
public class SingletonVerifier {
  private static final Logger LOGGER = LoggerFactory.getLogger(SingletonVerifier.class);

  private final String[] basePackages;
  private final Comparator CLASS_THEN_NAME = new Comparator() {
    @Override
    public int compare(Field dis, Field dat) {
      int status = dis.getDeclaringClass().getName().compareTo(dat.getDeclaringClass().getName());
      if (status != 0) {
        return status;
      }
      return dis.getName().compareTo(dat.getName());
    }
  };

  /**
   * Instantiates a new Singleton verifier.
   *
   * @param basePackages the base backage
   */
  public SingletonVerifier(String... basePackages) {
    this.basePackages = basePackages;
  }

  /**
   * Annotations for Spring Bean
   *
   * @return the annotations
   */
  protected Collection> getAnnotations() {
    return new ArrayList<>(Arrays
        .asList(Component.class, Controller.class, Repository.class, Service.class, RestController.class));
  }

  /**
   * Fail on instance fields.
   */
  public void failOnInstanceFields() {
    final Set instanceFields = getInstanceFields();
    if (!instanceFields.isEmpty()) {
      System.out.println("=============================");
      System.out.println("Instance level fields are defined in a Singleton Object");
      int counter = 0;
      for (Field field : instanceFields) {
        System.out.println(++counter + "\t" + field);
      }
      System.out.println("=============================");
      System.out.println("Instance variables found in Singleton Objects. Check above logs.");
    }
    assert instanceFields.isEmpty();
  }

  /**
   * Gets instance fields.
   *
   * @return the instance fields
   */
  public Set getInstanceFields() {
    final Set instanceFields = new TreeSet<>(CLASS_THEN_NAME);
    Set> services = new HashSet<>();
    for (Class clazz : getAnnotations()) {
      for (String basePackage : basePackages) {
        services.addAll(new Reflections(basePackage).getTypesAnnotatedWith(clazz));
      }
    }
    for (Class service : services) {
      instanceFields.addAll(getInstanceFields(service));
    }
    return instanceFields;
  }

  /**
   * Is test class boolean.
   *
   * @param service the service
   * @return the boolean
   */
  public static boolean isTestClass(Class service) {
    File file = new File(".");
    String fileName = service.getName();
    return isTestClass(file, fileName, 2);
  }

  /**
   * Is test class boolean.
   *
   * @param folder   the folder
   * @param fileName the file name
   * @param depth    the depth
   * @return the boolean
   */
  public static boolean isTestClass(File folder, String fileName, int depth) {
    if (depth < 0) {
      return false;
    }
    if (!folder.isDirectory()) {
      return false;
    }
    if (new File(folder, fileName).exists()) {
      return true;
    }
    File[] files = folder.listFiles();
    if (files == null) {
      return false;
    }
    for (File file : files) {
      if (isTestClass(file, fileName, depth - 1)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Populate fields.
   *
   * @param clazz  the clazz
   * @param fields the fields
   */
  public static void populateFields(Class clazz, Set fields) {
    if (clazz == null || clazz == Object.class) {
      return;
    }
    Field[] declaredFields = clazz.getDeclaredFields();
    if (declaredFields != null) {
      Collections.addAll(fields, declaredFields);
    }
    populateFields(clazz.getSuperclass(), fields);
  }

  /**
   * Is service interface boolean.
   *
   * @param basePackages the base packages
   * @param type         the type
   * @return the boolean
   */
  public static boolean isServiceInterface(String[] basePackages, Class type) {
    if (isTestClass(type)) {
      return true;
    }
    final Set> allSubTypes = new HashSet<>();
    for (String basePackage : basePackages) {
      Collection> subTypes = new Reflections(basePackage).getSubTypesOf(type);
      if (subTypes != null) {
        allSubTypes.addAll(subTypes);
      }
    }
    Iterator> iterator = allSubTypes.iterator();
    while (iterator.hasNext()) {
      if (isTestClass(iterator.next())) {
        iterator.remove();
      }
    }
    return allSubTypes.size() == 1;
  }

  /**
   * Gets instance fields.
   *
   * @param service the service
   * @return the instance fields
   */
  public Set getInstanceFields(Class service) {
    final Set instanceFields = new TreeSet<>(CLASS_THEN_NAME);
    try {
      if (isTestClass(service)) {
        return instanceFields;
      }
      Scope scope = service.getAnnotation(Scope.class);
      if (scope != null && !scope.scopeName().equalsIgnoreCase(ConfigurableBeanFactory.SCOPE_SINGLETON)) {
        return instanceFields;
      }
      Set allFields = new HashSet<>();
      populateFields(service, allFields);
      for (Field field : allFields) {
        if (Modifier.isFinal(field.getModifiers())) {
          continue;
        }
        if (field.isAnnotationPresent(IgnoredInstance.class)) {
          continue;
        }
        if (field.isAnnotationPresent(Autowired.class)) {
          continue;
        }
        if (field.isAnnotationPresent(Value.class)) {
          continue;
        }
        if (isServiceInterface(basePackages, field.getType())) {
          continue;
        }
        if (isJacocoField(field)) {
          continue;
        }
        if (!isInstance(field)) {
          continue;
        }
        instanceFields.add(field);
      }
    } catch (Throwable e) {
      LOGGER.warn("Exception in service class[{}] - {}", service, e.getMessage());
    }
    return instanceFields;
  }

  private boolean isJacocoField(Field field) {
    final int modifiers = field.getModifiers();
    if (!Modifier.isStatic(modifiers)) {
      return false;
    }
    if (!Modifier.isTransient(modifiers)) {
      return false;
    }
    if (!Modifier.isPrivate(modifiers)) {
      return false;
    }
    if (!field.getName().equalsIgnoreCase("$jacocoData")) {
      return false;
    }
    if (field.getType() != boolean[].class) {
      return false;
    }
    return true;
  }

  /**
   * Hook to add more validations.
   *
   * @param field the field
   * @return the boolean
   */
  protected boolean isInstance(Field field) {
    return true;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy