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

ai.timefold.solver.spring.boot.autoconfigure.IncludeAbstractClassesEntityScanner Maven / Gradle / Ivy

Go to download

Timefold solves planning problems. This lightweight, embeddable planning engine implements powerful and scalable algorithms to optimize business resource scheduling and planning. This module contains the Spring Boot autoconfigure.

There is a newer version: 1.16.0
Show newest version
package ai.timefold.solver.spring.boot.autoconfigure;

import static ai.timefold.solver.spring.boot.autoconfigure.util.LambdaUtils.rethrowFunction;
import static java.util.Collections.emptyList;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.domain.EntityScanPackages;
import org.springframework.boot.autoconfigure.domain.EntityScanner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.util.ClassUtils;

public class IncludeAbstractClassesEntityScanner extends EntityScanner {

    private final ApplicationContext context;

    public IncludeAbstractClassesEntityScanner(ApplicationContext context) {
        super(context);
        this.context = context;
    }

    public  Class findFirstImplementingClass(Class targetClass) {
        List> classes = findImplementingClassList(targetClass);
        if (!classes.isEmpty()) {
            return classes.get(0);
        }
        return null;
    }

    private Set findPackages() {
        Set packages = new HashSet<>();
        packages.addAll(AutoConfigurationPackages.get(context));
        EntityScanPackages entityScanPackages = EntityScanPackages.get(context);
        packages.addAll(entityScanPackages.getPackageNames());
        return packages;
    }

    public  List> findImplementingClassList(Class targetClass) {
        if (!AutoConfigurationPackages.has(context)) {
            return emptyList();
        }
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.setEnvironment(context.getEnvironment());
        scanner.setResourceLoader(context);
        scanner.addIncludeFilter(new AssignableTypeFilter(targetClass));
        Set packages = findPackages();
        return packages.stream()
                .flatMap(basePackage -> scanner.findCandidateComponents(basePackage).stream())
                // findCandidateComponents can return the same package for different base packages
                .distinct()
                .sorted(Comparator.comparing(BeanDefinition::getBeanClassName))
                .map(candidate -> {
                    try {
                        return (Class) ClassUtils.forName(candidate.getBeanClassName(), context.getClassLoader())
                                .asSubclass(targetClass);
                    } catch (ClassNotFoundException e) {
                        throw new IllegalStateException("The %s class (%s) cannot be found."
                                .formatted(targetClass.getSimpleName(), candidate.getBeanClassName()), e);
                    }
                })
                .collect(Collectors.toList());
    }

    @SafeVarargs
    public final List> findClassesWithAnnotation(Class... annotations) {
        if (!AutoConfigurationPackages.has(context)) {
            return emptyList();
        }
        Set packages = findPackages();
        return packages.stream().flatMap(rethrowFunction(
                basePackage -> findAllClassesUsingClassLoader(this.context.getClassLoader(), basePackage).stream()))
                .filter(clazz -> hasAnyFieldOrMethodWithAnnotation(clazz, annotations))
                .toList();
    }

    private boolean hasAnyFieldOrMethodWithAnnotation(Class clazz, Class[] annotations) {
        List fieldList = List.of(clazz.getDeclaredFields());
        List methodList = List.of(clazz.getDeclaredMethods());
        return List.of(annotations).stream().anyMatch(a -> fieldList.stream().anyMatch(f -> f.getAnnotation(a) != null)
                || methodList.stream().anyMatch(m -> m.getDeclaredAnnotation(a) != null));
    }

    public boolean hasSolutionOrEntityClasses() {
        try {
            return !scan(PlanningSolution.class).isEmpty() || !scan(PlanningEntity.class).isEmpty();
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException("Scanning for @%s and @%s annotations failed."
                    .formatted(PlanningSolution.class.getSimpleName(), PlanningEntity.class.getSimpleName()), e);
        }
    }

    public Class findFirstSolutionClass() {
        Set> solutionClassSet;
        try {
            solutionClassSet = scan(PlanningSolution.class);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(
                    "Scanning for @%s annotations failed.".formatted(PlanningSolution.class.getSimpleName()), e);
        }
        return solutionClassSet.iterator().next();
    }

    public List> findEntityClassList() {
        Set> entityClassSet;
        try {
            entityClassSet = scan(PlanningEntity.class);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException("Scanning for @%s failed.".formatted(PlanningEntity.class.getSimpleName()), e);
        }
        return new ArrayList<>(entityClassSet);
    }

    private Set> findAllClassesUsingClassLoader(ClassLoader classLoader, String packageName) throws IOException {
        try (InputStream stream = classLoader.getResourceAsStream(packageName.replaceAll("[.]", "/"));
                BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
            return reader.lines()
                    .filter(line -> line.endsWith(".class"))
                    .map(className -> packageName + "." + className.substring(0, className.lastIndexOf('.')))
                    .map(className -> getClass(classLoader, className))
                    .collect(Collectors.toSet());
        }
    }

    private Class getClass(ClassLoader classLoader, String className) {
        try {
            return Class.forName(className, false, classLoader);
        } catch (ClassNotFoundException e) {
            // ignore the exception
        }
        return null;
    }

    @Override
    protected ClassPathScanningCandidateComponentProvider
            createClassPathScanningCandidateComponentProvider(ApplicationContext context) {
        return new ClassPathScanningCandidateComponentProvider(false) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                AnnotationMetadata metadata = beanDefinition.getMetadata();
                // Do not exclude abstract classes nor interfaces
                return metadata.isIndependent();
            }
        };
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy