silentbalanceyh/vertx-zero

View on GitHub
vertx-gaia/vertx-co/src/main/environment/io/vertx/up/runtime/ZeroPack.java

Summary

Maintainability
A
0 mins
Test Coverage
package io.vertx.up.runtime;

import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import io.horizon.eon.VPath;
import io.horizon.uca.log.Annal;
import io.vertx.core.impl.ConcurrentHashSet;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.up.fn.Fn;
import io.vertx.up.util.Ut;
import org.junit.runner.RunWith;

import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

/**
 * ZeroPack the package to extract classes.
 */
@SuppressWarnings("all")
public final class ZeroPack {

    private static final Annal LOGGER = Annal.get(ZeroPack.class);
    private static final Set<String> FILTERS = new TreeSet<>();
    private static final Set<Class<?>> CLASSES = new ConcurrentHashSet<>();

    static {
        /*
         * Read configuration to fill FILTERS;
         */
        final JsonObject filter = Ut.ioJObject(VPath.SERVER.INTERNAL_PACKAGE);
        if (filter.containsKey("skip")) {
            final JsonArray skiped = filter.getJsonArray("skip");
            if (Objects.nonNull(skiped)) {
                LOGGER.info(Info.IGNORES, skiped.encodePrettily());
                FILTERS.addAll(skiped.getList());
            }
        }
    }

    private ZeroPack() {
    }

    @SuppressWarnings("all")
    public static Set<Class<?>> getClasses() {
        /*
         * Get all packages that will be scanned.
         */
        if (CLASSES.isEmpty()) {
            // final Set<String> packageDirs = PackHunter.getPackages();
            // packageDirs.add(Strings.DOT);
            /*
             * Debug in file
             */
            /*
            final JsonArray debugPkg = new JsonArray();
            packageDirs.forEach(debugPkg::add);
            Ut.ioOut("/Users/lang/Out/out-package.json", debugPkg); */
            /*
             * Debug in package
             * Here I have tested package in total when development & production environment both.
             * The scanned package count are the same, it means that
             * here is no error when capture package here.
             * The left thing is that we should be sure class counter are the same as also.
             * 1) Current project classes
             * 2) For zero extension module, we also should add dependency classes into result.
             */

            final Set<Class<?>> scanned = getClassesInternal();
            // multiClasses(packageDirs.toArray(new String[]{}));
            CLASSES.addAll(scanned.stream().parallel()
                //                .filter(type -> !type.isAnonymousClass())                      // Ko Anonymous
                //                .filter(type -> !type.isAnnotation())                          // Ko Annotation
                //                .filter(type -> !type.isEnum())                                // Ko Enum
                //                .filter(type -> Modifier.isPublic(type.getModifiers()))        // Ko non-public
                //                // Ko abstract class, because interface is abstract, single condition is invalid
                //                .filter(type -> !(Modifier.isAbstract(type.getModifiers()) && !type.isInterface()))
                //                // .filter(type -> !Modifier.isAbstract(type.getModifiers()))  // Because interface is abstract
                //                .filter(type -> !Modifier.isStatic(type.getModifiers()))       // Ko Static
                //                .filter(type -> !Throwable.class.isAssignableFrom(type))       // Ko Exception
                //                .filter(type -> !type.isAnnotationPresent(RunWith.class))      // Ko Test Class
                .filter(ZeroPack::validType)
                .collect(Collectors.toSet()));
            LOGGER.info(Info.CLASSES, String.valueOf(CLASSES.size()));
            /*
             * Debug in file
             */
            /*
            final Set<String> classSet = new TreeSet<>();
            CLASSES.forEach(clazz -> classSet.add(clazz.getName()));
            final JsonArray debugCls = new JsonArray();
            classSet.forEach(debugCls::add);
            Ut.ioOut("/Users/lang/Out/out.json", debugCls);*/
            // System.exit(0);
        }
        return CLASSES;
    }

    private static boolean validType(final Class<?> type) {
        return !type.isAnonymousClass()                             // Ko Anonymous
            && !type.isAnnotation()                                 // Ko Annotation
            && !type.isEnum()                                       // Ko Enum
            && Modifier.isPublic(type.getModifiers())               // Ko non-public
            // Ko abstract class, because interface is abstract, single condition is invalid
            && !(Modifier.isAbstract(type.getModifiers()) && !type.isInterface())
            && !Modifier.isStatic(type.getModifiers())              // Ko Static
            && !Throwable.class.isAssignableFrom(type)              // Ko Exception
            && !type.isAnnotationPresent(RunWith.class)             // Ko Test Class
            && ZeroPack.validMember(type);                          // Ko `Method/Field`
    }

    private static boolean validMember(final Class<?> type) {
        try {
            // Fix issue of Guice
            // java.lang.NoClassDefFoundError: camundajar/impl/scala/reflect/macros/blackbox/Context
            type.getDeclaredMethods();
            type.getDeclaredFields();
            return true;
        } catch (NoClassDefFoundError ex) {
            return false;
        }
    }

    @SuppressWarnings("all")
    private static Set<Class<?>> getClassesInternal() {
        // 保证线程安全
        final Set<Class<?>> classSet = Collections.synchronizedSet(new HashSet<>());
        Fn.jvmAt(() -> {
            final ClassPath cp = ClassPath.from(Thread.currentThread().getContextClassLoader());
            final ImmutableSet<ClassPath.ClassInfo> set = cp.getTopLevelClasses();
            final ConcurrentMap<String, Set<String>> packageMap = new ConcurrentHashMap<>();
            // 性能提高一倍,并行流处理更合理,暂时没发现明显问题
            set.parallelStream().forEach(cls -> {
                final String packageName = cls.getPackageName();
                final boolean skip = FILTERS.stream().anyMatch(packageName::startsWith);
                if (!skip) {
                    try {
                        classSet.add(Thread.currentThread().getContextClassLoader().loadClass(cls.getName()));
                    } catch (Throwable ex) {

                    }
                }
            });
        });
        return classSet;
    }
}