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 extends Annotation> 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 extends Class>> subTypes = new Reflections(basePackage).getSubTypesOf(type);
if (subTypes != null) {
allSubTypes.addAll(subTypes);
}
}
Iterator extends Class>> 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 - 2025 Weber Informatics LLC | Privacy Policy