package de.renew.plugin;

import java.net.URL;

/**
 * Used to delegate class loading requests to the class loaders
 * of the responsible modules.
 * Throws {@link ClassNotFoundException} if the class can not
 * be loaded by either the ApplicationClassLoader or a
 * module class loader.
 */
public class ModuleDelegationClassLoader extends ClassLoader {

    /**
     * Logger for logging purposes as specified by Apache Log4j
     */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(ModuleDelegationClassLoader.class);

    /**
     * The class loader returned by ClassLoader.getSystemClassLoader().
     */
    private final ClassLoader _applicationClassLoader;

    /**
     * Instantiates a new ModuleDelegationClassLoader. This class
     * will act as a proxy to delegate the {@link #loadClass(String, boolean)}
     * first to the ApplicationClassLoader, then to the individual
     * class loaders of the currently loaded modules.
     * <br>
     * Warning: For security reasons, this class has to always try
     * loading with the system class loader first, before delegating
     * to custom class loaders!
     *
     * @param applicationClassLoader the class loader returned by
     *                               ClassLoader.getSystemClassLoader()
     */
    public ModuleDelegationClassLoader(ClassLoader applicationClassLoader) {
        super(applicationClassLoader);
        this._applicationClassLoader = applicationClassLoader;
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // Try loading with ApplicationClassLoader
        try {
            return _applicationClassLoader.loadClass(name);
        } catch (ClassNotFoundException ignored) {
            // Class is not known to the ApplicationClassLoader
        }

        // Try delegating to internal class loader of module
        ClassLoader moduleClassLoader =
            PluginManager.getInstance().getModuleClassLoaderForClass(name);
        if (moduleClassLoader != null) {
            try {
                return moduleClassLoader.loadClass(name);
            } catch (ClassNotFoundException e) {
                LOGGER.debug(
                    "Found package, but couldn't find class " + name + " in loaded modules.");
            }
        }
        throw new ClassNotFoundException(
            "ModuleDelegationClassLoader was unable to find class " + name);
    }

    @Override
    public URL getResource(String name) {

        var cleanedName = name.startsWith("/") ? name.substring(1) : name;

        var searchString = cleanedName.substring(0, cleanedName.lastIndexOf('.')).replace('/', '.');

        ClassLoader moduleClassLoader =
            PluginManager.getInstance().getModuleClassLoaderForClass(searchString);

        if (moduleClassLoader != null) {
            return moduleClassLoader.getResource(cleanedName);
        }
        return super.getResource(name);
    }
}
