ClassUtils.java

package top.infra.common;

import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Sets.newLinkedHashSetWithExpectedSize;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.ArrayUtils.contains;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import lombok.SneakyThrows;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.io.Resource;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AbstractClassTestingTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

import commonsio.IOUtils;

public abstract class ClassUtils {

    private ClassUtils() {
    }

    public static boolean isPresent(final String className) {
        return isPresent(className, null);
    }

    /**
     * see: {@link org.springframework.boot.autoconfigure.condition.OnClassCondition}
     *
     * @param className   className
     * @param classLoader classLoader
     * @return isPresent
     */
    public static boolean isPresent(final String className, ClassLoader classLoader) {
        if (classLoader == null) {
            classLoader = org.springframework.util.ClassUtils.getDefaultClassLoader();
        }
        try {
            forName(className, classLoader);
            return true;
        } catch (final Throwable ex) {
            return false;
        }
    }

    public static Class<?> forName(final String className, final ClassLoader classLoader) throws ClassNotFoundException {
        if (classLoader != null) {
            return classLoader.loadClass(className);
        }
        return Class.forName(className);
    }

    public static Optional<Class<?>> forName(final String className) {
        try {
            final ClassLoader classLoader = org.springframework.util.ClassUtils.getDefaultClassLoader();
            final Class<?> result;
            if (classLoader != null) {
                result = classLoader.loadClass(className);
            } else {
                result = Class.forName(className);
            }
            return Optional.ofNullable(result);
        } catch (final ClassNotFoundException ex) {
            return Optional.empty();
        }
    }

    public static void printClassLoader(final Class<?> target) {
        ClassLoader classLoader = target.getClassLoader();
        if (classLoader instanceof URLClassLoader) {
            System.out.println(target.getName()
                + "/classLoader = "
                + classLoader
                + "/URLs/" + Arrays.asList(((URLClassLoader) classLoader).getURLs()));
        } else {
            System.out.println(target.getName() + "/classLoader = " + classLoader);
        }
    }

    public static final class FileAndClasspathUtils {

        private FileAndClasspathUtils() {
        }

        /**
         * Create if not exists.
         *
         * @param fileFullPath path
         */
        @SneakyThrows
        public static void ensureFile(final String fileFullPath) {
            checkState(isNotBlank(fileFullPath), "file undefined. file: %s", fileFullPath);
            final File file = new File(fileFullPath);
            if (!file.exists()) {
                checkState(file.createNewFile(), "file create failed. file: %s", fileFullPath);
            }
            checkState(file.canRead() && file.canWrite(),
                "file can't read or write. file: %s", fileFullPath);
        }

        /**
         * Parse file lines.
         *
         * @param resource resource
         * @param mapper   line mapper
         * @param <T>      type
         * @return list
         */
        @SneakyThrows
        public static <T> List<T> parseFile(final Resource resource, final Function<String, T> mapper) {
            return parseFile(resource.getInputStream(), mapper);
        }

        /**
         * Parse file lines.
         *
         * @param inputStream stream
         * @param mapper      line mapper
         * @param <T>         type
         * @return list
         */
        @SneakyThrows
        public static <T> List<T> parseFile(final InputStream inputStream, final Function<String, T> mapper) {
            return IOUtils.readLines(inputStream, UTF_8).stream() //
                .filter(line -> isNotBlank(line) && line.trim().charAt(0) != '#') //
                .map(mapper) //
                .collect(toList());
        }

        @SneakyThrows
        @SuppressWarnings("unchecked")
        private static <T> Class<T> classForName(final String className) {
            return (Class<T>) Class.forName(className);
        }

        /**
         * scan.
         *
         * @param basePackage   from where (package)
         * @param includeFilter filter
         * @param <T>           type
         * @return classes found
         */
        public static <T> Set<Class<T>> scan(final String basePackage, final TypeFilter includeFilter) {
            checkArgument(isNotBlank(basePackage));
            // log.info("domainEnums basePackage: {}", basePackage);

            final ClassPathScanningCandidateProvider provider = new ClassPathScanningCandidateProvider();
            provider.addIncludeFilter(includeFilter);
            final Set<BeanDefinition> beanDefinitions =
                provider.findCandidateComponents(basePackage.replaceAll("\\.", "/"));

            final Set<Class<T>> result = newLinkedHashSetWithExpectedSize(beanDefinitions.size());
            for (final BeanDefinition beanDefinition : beanDefinitions) {
                result.add(classForName(beanDefinition.getBeanClassName()));
            }
            return result;
        }

        public static class InterfaceFilter extends AbstractClassTestingTypeFilter {

            private final Class<?> type;
            private final boolean includeInterface;

            /**
             * filter find types of interface type.
             *
             * @param type             type
             * @param includeInterface include interface
             */
            public InterfaceFilter(final Class<?> type, final boolean includeInterface) {
                super();
                this.type = type;
                this.includeInterface = includeInterface;
            }

            @Override
            protected boolean match(final ClassMetadata metadata) {
                return contains(metadata.getInterfaceNames(), this.type.getName())
                    && (this.includeInterface || !metadata.isInterface());
            }
        }

        public static class AssignableFilter extends AssignableTypeFilter {

            private final boolean includeInterface;
            private final boolean includeAbstract;

            public AssignableFilter(final Class<?> targetType, final boolean includeInterface, final boolean includeAbstract) {
                super(targetType);
                this.includeInterface = includeInterface;
                this.includeAbstract = includeAbstract;
            }

            @Override
            public boolean match(final MetadataReader reader, final MetadataReaderFactory readerFactory) throws IOException {
                final ClassMetadata metadata = reader.getClassMetadata();
                final boolean match = super.match(reader, readerFactory);
                return match && //
                    (this.includeInterface || !metadata.isInterface()) && //
                    (this.includeAbstract || !metadata.isAbstract());
            }
        }

        /**
         * A ClassPathScanningCandidateComponentProvider.
         *
         * @author zhanghaolun
         */
        public static class ClassPathScanningCandidateProvider
            extends ClassPathScanningCandidateComponentProvider {

            /**
             * new a ClassPathScanningCandidateProvider.
             */
            public ClassPathScanningCandidateProvider() {
                super(false);
            }

            @Override
            protected boolean isCandidateComponent(final AnnotatedBeanDefinition beanDefinition) {
                return beanDefinition.getMetadata().isIndependent();
            }
        }
    }
}