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

io.microsphere.classloading.WindowsRedefinedClassLoader Maven / Gradle / Ivy

The newest version!
package io.microsphere.classloading;

import io.microsphere.io.IOUtils;
import io.microsphere.logging.Logger;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import static io.microsphere.collection.MapUtils.isEmpty;
import static io.microsphere.constants.SeparatorConstants.LINE_SEPARATOR;
import static io.microsphere.io.IOUtils.toByteArray;
import static io.microsphere.logging.LoggerFactory.getLogger;
import static io.microsphere.text.FormatUtils.format;
import static io.microsphere.util.StringUtils.split;

/**
 * The customized ClassLoader under Windows operating system to solve the case-insensitive
 * problem of the specified class (list) in the Class Path
 *
 * @author Mercy
 * @since 1.0.0
 */
class WindowsRedefinedClassLoader extends URLClassLoader {

    private static final String WINDOWS_REDEFINED_CLASSES_RESOURCE_NAME = "META-INF/windows-redefined-classes";

    private static final Logger logger = getLogger(WindowsRedefinedClassLoader.class);

    private static final Charset charset = StandardCharsets.UTF_8;

    /**
     * Class name as key and class resource directory URL as value
     */
    private static final SortedMap redefinedClassMetadata = new TreeMap<>();

    public WindowsRedefinedClassLoader(ClassLoader parent) {
        super(new URL[0], parent);
        loadRedefinedClassMetadata(parent);
    }

    @Override
    protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class klass = super.findLoadedClass(name);

        if (klass == null) {
            RedefinedClassMetadata metadata = getRedefinedClassMetadata(name);
            if (metadata != null) {
                klass = this.loadRedefinedClass(metadata, resolve);
            } else {
                klass = super.loadClass(name, resolve);
            }
        }

        return klass;
    }

    private Class loadRedefinedClass(RedefinedClassMetadata metadata, boolean resolve) throws ClassNotFoundException {
        Class result = null;
        String className = metadata.className;
        for (File packageDirectory : metadata.packageDirectories) {
            String classFileName = metadata.simpleClassName + ".class";
            // Because the Windows file system is not sensitive to the file name, the class file is obtained by directory file filtering
            File[] files = packageDirectory.listFiles(file -> classFileName.equals(file.getName()));
            if (files.length == 1) {
                File classFile = files[0];
                logger.debug("Class[name: {}] file [name: {}] found in Package directory [path: {}], about to execute ClassLoader.defineClass",
                        className, classFileName, packageDirectory.getAbsolutePath());
                try (FileInputStream inputStream = new FileInputStream(classFile)) {
                    byte[] byteCodes = toByteArray(inputStream);
                    result = super.defineClass(className, byteCodes, 0, byteCodes.length);
                } catch (IOException e) {
                    logger.error("Class[name: {}] file [path: {}] cannot be read!", className, classFile.getAbsolutePath());
                }
                break;
            }
        }

        if (result == null) {
            // ClassNotFoundException will be thrown by parent ClassLoader if the class cannot be found
            result = super.loadClass(className, resolve);
        }

        return result;
    }


    private void loadRedefinedClassMetadata(ClassLoader classLoader) {
        if (!isEmpty(redefinedClassMetadata)) {
            return;
        }
        SortedSet redefinedClassNames = loadRedefinedClassNames(classLoader);
        for (String redefinedClassName : redefinedClassNames) {
            RedefinedClassMetadata metadata = resolveRedefinedClassMetadata(redefinedClassName, classLoader);
            if (metadata != null) {
                redefinedClassMetadata.putIfAbsent(redefinedClassName, metadata);
            }
        }
    }

    private RedefinedClassMetadata resolveRedefinedClassMetadata(String className, ClassLoader classLoader) {
        int lastDotIndex = className.lastIndexOf('.');
        String packageName = lastDotIndex > 0 ? className.substring(0, lastDotIndex) : "";
        String packageRelativePath = packageName.replace('.', '/');
        List packageDirectories = new LinkedList<>();

        try {
            Enumeration packageResources = classLoader.getResources(packageRelativePath);
            while (packageResources.hasMoreElements()) {
                URL packageResource = packageResources.nextElement();
                if (!"file".equalsIgnoreCase(packageResource.getProtocol())) {
                    logger.debug("Class [name: {}] is located in a non-file system directory [path: {}], RedefinedClassMetadata does not need to be processed!",
                            className, packageResource);
                    continue;
                }
                File packageDirectory = new File(packageResource.getPath());
                packageDirectories.add(packageDirectory);
            }
        } catch (IOException e) {
            logger.error("The package resource [path: {}] for class [Name: {}] cannot be read by the classloader",
                    className, packageRelativePath);
        }

        if (packageDirectories.isEmpty()) {
            return null;
        }

        String simpleClassName = lastDotIndex > 0 ? className.substring(lastDotIndex + 1) : className;

        RedefinedClassMetadata metadata = new RedefinedClassMetadata();
        metadata.className = className;
        metadata.packageName = packageName;
        metadata.simpleClassName = simpleClassName;
        metadata.packageDirectories = packageDirectories;
        return metadata;
    }

    private static SortedSet loadRedefinedClassNames(ClassLoader classLoader) {
        String resourceName = WINDOWS_REDEFINED_CLASSES_RESOURCE_NAME;
        SortedSet redefinedClassNames = new TreeSet<>();
        try {
            Enumeration resources = classLoader.getResources(WINDOWS_REDEFINED_CLASSES_RESOURCE_NAME);
            while (resources.hasMoreElements()) {
                URL resource = resources.nextElement();
                try (InputStream inputStream = resource.openStream()) {
                    String configContent = IOUtils.toString(inputStream, charset);
                    String[] classNames = split(configContent, LINE_SEPARATOR);
                    redefinedClassNames.addAll(Arrays.asList(classNames));
                }
            }
        } catch (IOException e) {
            throw new IllegalStateException(format("Windows redefinition class manifest file] [- {}] read failed!", resourceName), e);
        }
        return redefinedClassNames;
    }

    private static RedefinedClassMetadata getRedefinedClassMetadata(String className) {
        return redefinedClassMetadata.get(className);
    }

    private static class RedefinedClassMetadata {

        private String className;

        private String packageName;

        private String simpleClassName;

        private List packageDirectories; // Multiple Classpaths of the companion package name may exist

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy