<commit_before>/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.modules;

import static java.security.AccessController.doPrivileged;
import static org.jboss.modules.management.ObjectProperties.property;

import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.jboss.modules.log.ModuleLogger;
import org.jboss.modules.management.DependencyInfo;
import org.jboss.modules.management.ModuleInfo;
import org.jboss.modules.management.ModuleLoaderMXBean;
import org.jboss.modules.management.ObjectProperties;
import org.jboss.modules.management.ResourceLoaderInfo;
import org.jboss.modules.ref.Reaper;
import org.jboss.modules.ref.Reference;
import org.jboss.modules.ref.WeakReference;

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

/**
 * A repository for modules, from which a module may be loaded by identifier.  Module loaders may additionally
 * delegate to one or more other module loaders.
 *
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @author <NAME>
 *
 * @apiviz.landmark
 */
public class ModuleLoader {

    private static final RuntimePermission ML_PERM = new RuntimePermission("canCreateModuleLoader");
    private static final RuntimePermission MODULE_REDEFINE_PERM = new RuntimePermission("canRedefineModule");
    private static final RuntimePermission MODULE_REDEFINE_ANY_PERM = new RuntimePermission("canRedefineAnyModule");
    private static final RuntimePermission MODULE_UNLOAD_ANY_PERM = new RuntimePermission("canUnloadAnyModule");
    private static final RuntimePermission MODULE_ITERATE_PERM = new RuntimePermission("canIterateModules");

    private static final AtomicInteger SEQ = new AtomicInteger(1);
    private static final Class<?>[] JUST_MODULE_IDENTIFIER = { ModuleIdentifier.class };

    private static volatile MBeanReg REG_REF = new TempMBeanReg();

    /**
     * A constant representing zero module finders.
     */
    public static final ModuleFinder[] NO_FINDERS = new ModuleFinder[0];

    private final ConcurrentMap<String, FutureModule> moduleMap = new ConcurrentHashMap<>();
    private final ModuleFinder[] finders;

    private final boolean canRedefine;
    private final ModuleLoaderMXBean mxBean;

    private final AtomicLong linkTime = new AtomicLong();
    private final AtomicLong loadTime = new AtomicLong();
    private final AtomicLong classLoadTime = new AtomicLong();
    private final AtomicInteger scanCount = new AtomicInteger();
    private final AtomicInteger raceCount = new AtomicInteger();
    private final AtomicInteger classCount = new AtomicInteger();

    private final boolean overridesFindModuleByIdentifier;
    private final boolean overridesPreloadModuleByIdentifier;

    ModuleLoader(boolean canRedefine, boolean skipRegister) {
        this(canRedefine, skipRegister, NO_FINDERS);
    }

    // Bypass security check for classes in this package
    ModuleLoader(boolean canRedefine, boolean skipRegister, ModuleFinder[] finders) {
        this.canRedefine = canRedefine;
        this.finders = finders;
        mxBean = skipRegister ? null : doPrivileged(new PrivilegedAction<ModuleLoaderMXBean>() {
            public ModuleLoaderMXBean run() {
                ObjectName objectName;
                try {
                    objectName = new ObjectName("jboss.modules", ObjectProperties.properties(property("type", "ModuleLoader"), property("name", ModuleLoader.this.getClass().getSimpleName() + "-" + Integer.toString(SEQ.incrementAndGet()))));
                } catch (MalformedObjectNameException e) {
                    return null;
                }
                try {
                    MXBeanImpl mxBean = new MXBeanImpl(ModuleLoader.this, objectName);
                    REG_REF.addMBean(objectName, mxBean);
                    return mxBean;
                } catch (Throwable ignored) {
                }
                return null;
            }
        });
        // determine if the subclass overrides the crusty old find-module-by-identifier method
        boolean foundFindModule = false;
        boolean foundPreloadModule = false;
        for (Class<? extends ModuleLoader> clazz = getClass(); clazz != ModuleLoader.class; clazz = clazz.getSuperclass().asSubclass(ModuleLoader.class)) {
            for (Method method : clazz.getDeclaredMethods()) {
                if ((method.getModifiers() & (Modifier.STATIC | Modifier.PRIVATE)) == 0) {
                    if (method.getName().equals("findModule")) {
                        if (ModuleSpec.class.isAssignableFrom(method.getReturnType())) {
                            if (Arrays.equals(method.getParameterTypes(), JUST_MODULE_IDENTIFIER)) {
                                foundFindModule = true;
                                if (foundPreloadModule) break;
                            }
                        }
                    } else if (method.getName().equals("preloadModule")) {
                        if (Module.class.isAssignableFrom(method.getReturnType())) {
                            if (Arrays.equals(method.getParameterTypes(), JUST_MODULE_IDENTIFIER)) {
                                foundPreloadModule = true;
                                if (foundFindModule) break;
                            }
                        }
                    }
                }
            }
        }
        overridesFindModuleByIdentifier = foundFindModule;
        overridesPreloadModuleByIdentifier = foundPreloadModule;
    }

    /**
     * Construct a new instance.
     */
    protected ModuleLoader() {
        this(NO_FINDERS);
    }

    /**
     * Construct a new instance.
     *
     * @param finders the module finders to search, in order
     */
    public ModuleLoader(final ModuleFinder[] finders) {
        this(checkPermissions(), false, safeClone(finders));
    }

    private static ModuleFinder[] safeClone(ModuleFinder[] finders) {
        if (finders == null || finders.length == 0) {
            return NO_FINDERS;
        }
        finders = finders.clone();
        for (ModuleFinder finder : finders) {
            if (finder == null) {
                throw new IllegalArgumentException("Module finder cannot be null");
            }
        }
        return finders;
    }

    private static boolean checkPermissions() {
        SecurityManager manager = System.getSecurityManager();
        if (manager == null) {
            return true;
        }
        manager.checkPermission(ML_PERM);
        try {
            manager.checkPermission(MODULE_REDEFINE_PERM);
            return true;
        } catch (SecurityException e) {
            return false;
        }
    }

    /**
     * Get the module loader for a class.
     *
     * @param clazz the class
     *
     * @return the module loader or {@code null} if the class's class loader does not belong to a module loader.
     */
    public static ModuleLoader forClass(Class<?> clazz) {
        final Module module = Module.forClass(clazz);
        if (module == null) {
            return null;
        }
        return module.getModuleLoader();
    }

    /**
     * Get the module loader for a class loader.
     *
     * @param classLoader the class loader
     *
     * @return the module loader or {@code null} if the class loader does not belong to a module loader.
     */
    public static ModuleLoader forClassLoader(ClassLoader classLoader) {
        final Module module = Module.forClassLoader(classLoader, true);
        if (module == null) {
            return null;
        }
        return module.getModuleLoader();
    }

    /**
     * Get the string representation of this module loader.
     *
     * @return the string representation
     */
    public String toString() {
        return String.format("%s@%x for finders %s", getClass().getSimpleName(), hashCode(), Arrays.toString(finders));
    }

    static void installMBeanServer() {
        REG_REF.installReal();
    }

    /**
     * Load a module based on an identifier.  This method delegates to {@link #preloadModule(String)} and then
     * links the returned module if necessary.
     *
     * @param identifier The module identifier
     * @return The loaded Module
     * @throws ModuleLoadException if the Module can not be loaded
     * @deprecated Use {@link #loadModule(String)} instead.
     */
    @Deprecated
    public final Module loadModule(ModuleIdentifier identifier) throws ModuleLoadException {
        return loadModule(identifier.toString());
    }

    /**
     * Load a module based on an identifier.  This method delegates to {@link #preloadModule(String)} and then
     * links the returned module if necessary.
     *
     * @param identifier The module identifier
     * @return The loaded Module
     * @throws ModuleLoadException if the Module can not be loaded
     */
    public final Module loadModule(String name) throws ModuleLoadException {
        final Module module = preloadModule(name);
        if (module == null) {
            throw new ModuleNotFoundException(name);
        }
        module.relinkIfNecessary();
        return module;
    }

    /**
     * Iterate the modules which can be located via this module loader.
     *
     * @param baseIdentifier the identifier to start with, or {@code null} to iterate all modules
     * @param recursive {@code true} to find recursively nested modules, {@code false} to only find immediately nested modules
     * @return an iterator for the modules in this module finder
     * @throws SecurityException if the caller does not have permission to iterate module loaders
     * @deprecated Use {@link #iterateModules(String, boolean)} instead.
     */
    @Deprecated
    public final Iterator<ModuleIdentifier> iterateModules(final ModuleIdentifier baseIdentifier, final boolean recursive) {
        return IteratorUtils.transformingIterator(iterateModules(baseIdentifier == null ? (String) null : baseIdentifier.toString(), recursive), ModuleIdentifier::fromString);
    }

    /**
     * Iterate the modules which can be located via this module loader.
     *
     * @param baseName the identifier to start with, or {@code null} to iterate all modules; ignored if this module
     * loader does not have a concept of nested modules
     * @param recursive {@code true} to find recursively nested modules, {@code false} to only find immediately nested
     * modules; ignored if this module finder does not have a concept of nested modules
     * @return an iterator for the modules in this module finder
     * @throws SecurityException if the caller does not have permission to iterate module loaders
     */
    public final Iterator<String> iterateModules(final String baseName, final boolean recursive) {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(MODULE_ITERATE_PERM);
        }
        return new Iterator<String>() {
            int idx;
            Iterator<String> nested;

            public boolean hasNext() {
                for (;;) {
                    while (nested == null) {
                        if (idx == finders.length) {
                            return false;
                        }
                        final ModuleFinder finder = finders[idx++];
                        if (finder instanceof IterableModuleFinder) {
                            nested = ((IterableModuleFinder) finder).iterateModules(baseName, recursive);
                        }
                    }

                    if (! nested.hasNext()) {
                        nested = null;
                    } else {
                        return true;
                    }
                }
            }

            public String next() {
                if (! hasNext()) {
                    throw new NoSuchElementException();
                }
                return nested.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    /**
     * This compatibility method just calls {@code preloadModule(methodIdentifier.toString())} and is usually not used;
     * if however a legacy subclass overrides this method, then it will be called instead of {@link #preloadModule(String)},
     * and that method will redirect to this one.
     *
     * @deprecated Use {@link #preloadModule(String)} instead.
     *
     * @param identifier the module identifier (must not be {@code null})
     * @return the load result, or {@code null} if the module is not found
     * @throws ModuleLoadException if an error occurs
     */
    @Deprecated
    protected Module preloadModule(ModuleIdentifier identifier) throws ModuleLoadException {
        if (overridesPreloadModuleByIdentifier) {
            return loadModuleLocal(identifier);
        } else {
            return preloadModule(identifier.toString());
        }
    }

    /**
     * Preload a module based on an identifier.  By default, no delegation is done and this method simply invokes
     * {@link #loadModuleLocal(String)}.  A delegating module loader may delegate to the appropriate module
     * loader based on loader-specific criteria (via the {@link #preloadModule(String, ModuleLoader)} method).
     *
     * @param name the module identifier
     * @return the load result, or {@code null} if the module is not found
     * @throws ModuleLoadException if an error occurs
     */
    protected Module preloadModule(String name) throws ModuleLoadException {
        if (overridesPreloadModuleByIdentifier) {
            return preloadModule(ModuleIdentifier.fromString(name));
        } else {
            return loadModuleLocal(name);
        }
    }

    /**
     * Preload an "exported" module based on an identifier.  By default this simply delegates to {@link #preloadModule(String)}.
     *
     * @param name the module identifier
     * @return the load result, or {@code null} if the module is not found
     * @throws ModuleLoadException if an error occurs
     */
    protected Module preloadExportedModule(String name) throws ModuleLoadException {
        return preloadModule(name);
    }

    /**
     * Preload an "exported" module based on an identifier.  By default this simply delegates to {@link #preloadModule(String)}.
     *
     * @param name the module identifier
     * @return the load result, or {@code null} if the module is not found
     * @throws ModuleLoadException if an error occurs
     * @deprecated Use {@link #preloadModule(String)} instead.
     */
    @Deprecated
    protected Module preloadExportedModule(ModuleIdentifier identifier) throws ModuleLoadException {
        return preloadModule(identifier.getName());
    }

    /**
     * Utility method to delegate to another module loader, accessible from subclasses.  The delegate module loader
     * will be queried for exported modules only.
     *
     * @param name the module name
     * @param moduleLoader the module loader to delegate to
     * @return the delegation result
     * @throws ModuleLoadException if an error occurs
     */
    protected static Module preloadModule(String name, ModuleLoader moduleLoader) throws ModuleLoadException {
        return moduleLoader.preloadExportedModule(name);
    }

    /**
     * Utility method to delegate to another module loader, accessible from subclasses.  The delegate module loader
     * will be queried for exported modules only.
     *
     * @param name the module name
     * @param moduleLoader the module loader to delegate to
     * @return the delegation result
     * @throws ModuleLoadException if an error occurs
     * @deprecated Use {@link #preloadModule(String, ModuleLoader)} instead.
     */
    @Deprecated
    protected static Module preloadModule(ModuleIdentifier identifier, ModuleLoader moduleLoader) throws ModuleLoadException {
        return moduleLoader.preloadExportedModule(identifier.toString());
    }

    /**
     * Try to load a module from this module loader.  Returns {@code null} if the module is not found.  The returned
     * module may not yet be resolved.  The returned module may have a different name than the given identifier if
     * the identifier is an alias for another module.
     *
     * @param moduleIdentifier the module identifier
     * @return the module
     * @throws ModuleLoadException if an error occurs while loading the module
     * @deprecated Use {@link #loadModuleLocal(String)} instead.
     */
    @Deprecated
    protected final Module loadModuleLocal(ModuleIdentifier moduleIdentifier) throws ModuleLoadException {
        return loadModuleLocal(moduleIdentifier.toString());
    }

    /**
     * Try to load a module from this module loader.  Returns {@code null} if the module is not found.  The returned
     * module may not yet be resolved.  The returned module may have a different name than the given identifier if
     * the identifier is an alias for another module.
     *
     * @param name the module name
     * @return the module
     * @throws ModuleLoadException if an error occurs while loading the module
     */
    protected final Module loadModuleLocal(String name) throws ModuleLoadException {
        FutureModule futureModule = moduleMap.get(name);
        if (futureModule != null) {
            return futureModule.getModule();
        }

        FutureModule newFuture = new FutureModule(name);
        futureModule = moduleMap.putIfAbsent(name, newFuture);
        if (futureModule != null) {
            return futureModule.getModule();
        }

        boolean ok = false;
        try {
            final ModuleLogger log = Module.log;
            log.trace("Locally loading module %s from %s", name, this);
            final long startTime = Metrics.getCurrentCPUTime();
            final ModuleSpec moduleSpec = findModule(name);
            loadTime.addAndGet(Metrics.getCurrentCPUTime() - startTime);
            if (moduleSpec == null) {
                log.trace("Module %s not found from %s", name, this);
                return null;
            }
            if (! moduleSpec.getName().equals(name)) {
                throw new ModuleLoadException("Module loader found a module with the wrong name");
            }
            final Module module;
            if ( moduleSpec instanceof AliasModuleSpec) {
                final String aliasName = ((AliasModuleSpec) moduleSpec).getAliasName();
                try {
                    newFuture.setModule(module = loadModuleLocal(aliasName));
                } catch (RuntimeException | Error e) {
                    log.trace(e, "Failed to load module %s (alias for %s)", name, aliasName);
                    throw e;
                }
            } else {
                module = defineModule((ConcreteModuleSpec) moduleSpec, newFuture);
            }
            log.trace("Loaded module %s from %s", name, this);
            ok = true;
            return module;
        } finally {
            if (! ok) {
                newFuture.setModule(null);
                moduleMap.remove(name, newFuture);
            }
        }
    }

    /**
     * Find an already-loaded module, returning {@code null} if the module isn't currently loaded.  May block
     * while the loaded state of the module is in question (if the module is being concurrently loaded from another
     * thread, for example).
     *
     * @param identifier the module identifier
     * @return the module, or {@code null} if it wasn't found
     * @deprecated Use {@link #findLoadedModuleLocal(String)} instead.
     */
    @Deprecated
    protected final Module findLoadedModuleLocal(ModuleIdentifier identifier) {
        return findLoadedModuleLocal(identifier.toString());
    }

    /**
     * Find an already-loaded module, returning {@code null} if the module isn't currently loaded.  May block
     * while the loaded state of the module is in question (if the module is being concurrently loaded from another
     * thread, for example).
     *
     * @param name the module identifier
     * @return the module, or {@code null} if it wasn't found
     */
    protected final Module findLoadedModuleLocal(String name) {
        FutureModule futureModule = moduleMap.get(name);
        if (futureModule != null) {
            try {
                return futureModule.getModule();
            } catch (ModuleNotFoundException e) {
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * Unload a module from this module loader.  Note that this has no effect on existing modules which refer to the
     * module being unloaded.  Also, only modules from the current module loader can be unloaded.  Unloading the same
     * module more than once has no additional effect.  This method only removes the mapping for the module; any running
     * threads which are currently accessing or linked to the module will continue to function, however attempts to load
     * this module will fail until a new module is loaded with the same name.  Once this happens, if all references to
     * the previous module are not cleared, the same module may be loaded more than once, causing possible class duplication
     * and class cast exceptions if proper care is not taken.
     *
     * @param module the module to unload
     * @throws SecurityException if an attempt is made to unload a module which does not belong to this module loader
     * @throws SecurityException if the module was not defined by this module loader
     */
    protected final void unloadModuleLocal(Module module) throws SecurityException {
        final ModuleLoader moduleLoader = module.getModuleLoader();
        if (moduleLoader != this) {
            throw new SecurityException("Attempted to unload " + module + " from a different module loader");
        }
        final ModuleIdentifier id = module.getIdentifier();
        final FutureModule futureModule = moduleMap.get(id);
        if (futureModule != null && futureModule.module == module) {
            moduleMap.remove(id, futureModule);
        }
    }

    /**
     * This compatibility method just calls {@code findModule(methodIdentifier.toString())} and is usually not used;
     * if however a legacy subclass overrides this method, then it will be called instead of {@link #findModule(String)},
     * and that method will redirect to this one.
     *
     * @deprecated Use {@link #findModule(String)} instead.
     *
     * @param moduleIdentifier the module identifier
     * @return the module specification
     * @throws ModuleLoadException
     */
    @Deprecated
    protected ModuleSpec findModule(final ModuleIdentifier moduleIdentifier) throws ModuleLoadException {
        if (overridesFindModuleByIdentifier) {
            return findModule0(moduleIdentifier.toString());
        } else {
            return findModule(moduleIdentifier.toString());
        }
    }

    /**
     * Find a Module's specification in this ModuleLoader by its identifier.  This can be overriden by sub-classes to
     * implement the Module loading strategy for this loader.  The default implementation iterates the module finders<commit_msg>[MODULES-256] Fixing module unload issue<commit_after>/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.modules;

import static java.security.AccessController.doPrivileged;
import static org.jboss.modules.management.ObjectProperties.property;

import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.jboss.modules.log.ModuleLogger;
import org.jboss.modules.management.DependencyInfo;
import org.jboss.modules.management.ModuleInfo;
import org.jboss.modules.management.ModuleLoaderMXBean;
import org.jboss.modules.management.ObjectProperties;
import org.jboss.modules.management.ResourceLoaderInfo;
import org.jboss.modules.ref.Reaper;
import org.jboss.modules.ref.Reference;
import org.jboss.modules.ref.WeakReference;

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

/**
 * A repository for modules, from which a module may be loaded by identifier.  Module loaders may additionally
 * delegate to one or more other module loaders.
 *
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @author <NAME>
 *
 * @apiviz.landmark
 */
public class ModuleLoader {

    private static final RuntimePermission ML_PERM = new RuntimePermission("canCreateModuleLoader");
    private static final RuntimePermission MODULE_REDEFINE_PERM = new RuntimePermission("canRedefineModule");
    private static final RuntimePermission MODULE_REDEFINE_ANY_PERM = new RuntimePermission("canRedefineAnyModule");
    private static final RuntimePermission MODULE_UNLOAD_ANY_PERM = new RuntimePermission("canUnloadAnyModule");
    private static final RuntimePermission MODULE_ITERATE_PERM = new RuntimePermission("canIterateModules");

    private static final AtomicInteger SEQ = new AtomicInteger(1);
    private static final Class<?>[] JUST_MODULE_IDENTIFIER = { ModuleIdentifier.class };

    private static volatile MBeanReg REG_REF = new TempMBeanReg();

    /**
     * A constant representing zero module finders.
     */
    public static final ModuleFinder[] NO_FINDERS = new ModuleFinder[0];

    private final ConcurrentMap<String, FutureModule> moduleMap = new ConcurrentHashMap<>();
    private final ModuleFinder[] finders;

    private final boolean canRedefine;
    private final ModuleLoaderMXBean mxBean;

    private final AtomicLong linkTime = new AtomicLong();
    private final AtomicLong loadTime = new AtomicLong();
    private final AtomicLong classLoadTime = new AtomicLong();
    private final AtomicInteger scanCount = new AtomicInteger();
    private final AtomicInteger raceCount = new AtomicInteger();
    private final AtomicInteger classCount = new AtomicInteger();

    private final boolean overridesFindModuleByIdentifier;
    private final boolean overridesPreloadModuleByIdentifier;

    ModuleLoader(boolean canRedefine, boolean skipRegister) {
        this(canRedefine, skipRegister, NO_FINDERS);
    }

    // Bypass security check for classes in this package
    ModuleLoader(boolean canRedefine, boolean skipRegister, ModuleFinder[] finders) {
        this.canRedefine = canRedefine;
        this.finders = finders;
        mxBean = skipRegister ? null : doPrivileged(new PrivilegedAction<ModuleLoaderMXBean>() {
            public ModuleLoaderMXBean run() {
                ObjectName objectName;
                try {
                    objectName = new ObjectName("jboss.modules", ObjectProperties.properties(property("type", "ModuleLoader"), property("name", ModuleLoader.this.getClass().getSimpleName() + "-" + Integer.toString(SEQ.incrementAndGet()))));
                } catch (MalformedObjectNameException e) {
                    return null;
                }
                try {
                    MXBeanImpl mxBean = new MXBeanImpl(ModuleLoader.this, objectName);
                    REG_REF.addMBean(objectName, mxBean);
                    return mxBean;
                } catch (Throwable ignored) {
                }
                return null;
            }
        });
        // determine if the subclass overrides the crusty old find-module-by-identifier method
        boolean foundFindModule = false;
        boolean foundPreloadModule = false;
        for (Class<? extends ModuleLoader> clazz = getClass(); clazz != ModuleLoader.class; clazz = clazz.getSuperclass().asSubclass(ModuleLoader.class)) {
            for (Method method : clazz.getDeclaredMethods()) {
                if ((method.getModifiers() & (Modifier.STATIC | Modifier.PRIVATE)) == 0) {
                    if (method.getName().equals("findModule")) {
                        if (ModuleSpec.class.isAssignableFrom(method.getReturnType())) {
                            if (Arrays.equals(method.getParameterTypes(), JUST_MODULE_IDENTIFIER)) {
                                foundFindModule = true;
                                if (foundPreloadModule) break;
                            }
                        }
                    } else if (method.getName().equals("preloadModule")) {
                        if (Module.class.isAssignableFrom(method.getReturnType())) {
                            if (Arrays.equals(method.getParameterTypes(), JUST_MODULE_IDENTIFIER)) {
                                foundPreloadModule = true;
                                if (foundFindModule) break;
                            }
                        }
                    }
                }
            }
        }
        overridesFindModuleByIdentifier = foundFindModule;
        overridesPreloadModuleByIdentifier = foundPreloadModule;
    }

    /**
     * Construct a new instance.
     */
    protected ModuleLoader() {
        this(NO_FINDERS);
    }

    /**
     * Construct a new instance.
     *
     * @param finders the module finders to search, in order
     */
    public ModuleLoader(final ModuleFinder[] finders) {
        this(checkPermissions(), false, safeClone(finders));
    }

    private static ModuleFinder[] safeClone(ModuleFinder[] finders) {
        if (finders == null || finders.length == 0) {
            return NO_FINDERS;
        }
        finders = finders.clone();
        for (ModuleFinder finder : finders) {
            if (finder == null) {
                throw new IllegalArgumentException("Module finder cannot be null");
            }
        }
        return finders;
    }

    private static boolean checkPermissions() {
        SecurityManager manager = System.getSecurityManager();
        if (manager == null) {
            return true;
        }
        manager.checkPermission(ML_PERM);
        try {
            manager.checkPermission(MODULE_REDEFINE_PERM);
            return true;
        } catch (SecurityException e) {
            return false;
        }
    }

    /**
     * Get the module loader for a class.
     *
     * @param clazz the class
     *
     * @return the module loader or {@code null} if the class's class loader does not belong to a module loader.
     */
    public static ModuleLoader forClass(Class<?> clazz) {
        final Module module = Module.forClass(clazz);
        if (module == null) {
            return null;
        }
        return module.getModuleLoader();
    }

    /**
     * Get the module loader for a class loader.
     *
     * @param classLoader the class loader
     *
     * @return the module loader or {@code null} if the class loader does not belong to a module loader.
     */
    public static ModuleLoader forClassLoader(ClassLoader classLoader) {
        final Module module = Module.forClassLoader(classLoader, true);
        if (module == null) {
            return null;
        }
        return module.getModuleLoader();
    }

    /**
     * Get the string representation of this module loader.
     *
     * @return the string representation
     */
    public String toString() {
        return String.format("%s@%x for finders %s", getClass().getSimpleName(), hashCode(), Arrays.toString(finders));
    }

    static void installMBeanServer() {
        REG_REF.installReal();
    }

    /**
     * Load a module based on an identifier.  This method delegates to {@link #preloadModule(String)} and then
     * links the returned module if necessary.
     *
     * @param identifier The module identifier
     * @return The loaded Module
     * @throws ModuleLoadException if the Module can not be loaded
     * @deprecated Use {@link #loadModule(String)} instead.
     */
    @Deprecated
    public final Module loadModule(ModuleIdentifier identifier) throws ModuleLoadException {
        return loadModule(identifier.toString());
    }

    /**
     * Load a module based on an identifier.  This method delegates to {@link #preloadModule(String)} and then
     * links the returned module if necessary.
     *
     * @param identifier The module identifier
     * @return The loaded Module
     * @throws ModuleLoadException if the Module can not be loaded
     */
    public final Module loadModule(String name) throws ModuleLoadException {
        final Module module = preloadModule(name);
        if (module == null) {
            throw new ModuleNotFoundException(name);
        }
        module.relinkIfNecessary();
        return module;
    }

    /**
     * Iterate the modules which can be located via this module loader.
     *
     * @param baseIdentifier the identifier to start with, or {@code null} to iterate all modules
     * @param recursive {@code true} to find recursively nested modules, {@code false} to only find immediately nested modules
     * @return an iterator for the modules in this module finder
     * @throws SecurityException if the caller does not have permission to iterate module loaders
     * @deprecated Use {@link #iterateModules(String, boolean)} instead.
     */
    @Deprecated
    public final Iterator<ModuleIdentifier> iterateModules(final ModuleIdentifier baseIdentifier, final boolean recursive) {
        return IteratorUtils.transformingIterator(iterateModules(baseIdentifier == null ? (String) null : baseIdentifier.toString(), recursive), ModuleIdentifier::fromString);
    }

    /**
     * Iterate the modules which can be located via this module loader.
     *
     * @param baseName the identifier to start with, or {@code null} to iterate all modules; ignored if this module
     * loader does not have a concept of nested modules
     * @param recursive {@code true} to find recursively nested modules, {@code false} to only find immediately nested
     * modules; ignored if this module finder does not have a concept of nested modules
     * @return an iterator for the modules in this module finder
     * @throws SecurityException if the caller does not have permission to iterate module loaders
     */
    public final Iterator<String> iterateModules(final String baseName, final boolean recursive) {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(MODULE_ITERATE_PERM);
        }
        return new Iterator<String>() {
            int idx;
            Iterator<String> nested;

            public boolean hasNext() {
                for (;;) {
                    while (nested == null) {
                        if (idx == finders.length) {
                            return false;
                        }
                        final ModuleFinder finder = finders[idx++];
                        if (finder instanceof IterableModuleFinder) {
                            nested = ((IterableModuleFinder) finder).iterateModules(baseName, recursive);
                        }
                    }

                    if (! nested.hasNext()) {
                        nested = null;
                    } else {
                        return true;
                    }
                }
            }

            public String next() {
                if (! hasNext()) {
                    throw new NoSuchElementException();
                }
                return nested.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    /**
     * This compatibility method just calls {@code preloadModule(methodIdentifier.toString())} and is usually not used;
     * if however a legacy subclass overrides this method, then it will be called instead of {@link #preloadModule(String)},
     * and that method will redirect to this one.
     *
     * @deprecated Use {@link #preloadModule(String)} instead.
     *
     * @param identifier the module identifier (must not be {@code null})
     * @return the load result, or {@code null} if the module is not found
     * @throws ModuleLoadException if an error occurs
     */
    @Deprecated
    protected Module preloadModule(ModuleIdentifier identifier) throws ModuleLoadException {
        if (overridesPreloadModuleByIdentifier) {
            return loadModuleLocal(identifier);
        } else {
            return preloadModule(identifier.toString());
        }
    }

    /**
     * Preload a module based on an identifier.  By default, no delegation is done and this method simply invokes
     * {@link #loadModuleLocal(String)}.  A delegating module loader may delegate to the appropriate module
     * loader based on loader-specific criteria (via the {@link #preloadModule(String, ModuleLoader)} method).
     *
     * @param name the module identifier
     * @return the load result, or {@code null} if the module is not found
     * @throws ModuleLoadException if an error occurs
     */
    protected Module preloadModule(String name) throws ModuleLoadException {
        if (overridesPreloadModuleByIdentifier) {
            return preloadModule(ModuleIdentifier.fromString(name));
        } else {
            return loadModuleLocal(name);
        }
    }

    /**
     * Preload an "exported" module based on an identifier.  By default this simply delegates to {@link #preloadModule(String)}.
     *
     * @param name the module identifier
     * @return the load result, or {@code null} if the module is not found
     * @throws ModuleLoadException if an error occurs
     */
    protected Module preloadExportedModule(String name) throws ModuleLoadException {
        return preloadModule(name);
    }

    /**
     * Preload an "exported" module based on an identifier.  By default this simply delegates to {@link #preloadModule(String)}.
     *
     * @param name the module identifier
     * @return the load result, or {@code null} if the module is not found
     * @throws ModuleLoadException if an error occurs
     * @deprecated Use {@link #preloadModule(String)} instead.
     */
    @Deprecated
    protected Module preloadExportedModule(ModuleIdentifier identifier) throws ModuleLoadException {
        return preloadModule(identifier.getName());
    }

    /**
     * Utility method to delegate to another module loader, accessible from subclasses.  The delegate module loader
     * will be queried for exported modules only.
     *
     * @param name the module name
     * @param moduleLoader the module loader to delegate to
     * @return the delegation result
     * @throws ModuleLoadException if an error occurs
     */
    protected static Module preloadModule(String name, ModuleLoader moduleLoader) throws ModuleLoadException {
        return moduleLoader.preloadExportedModule(name);
    }

    /**
     * Utility method to delegate to another module loader, accessible from subclasses.  The delegate module loader
     * will be queried for exported modules only.
     *
     * @param name the module name
     * @param moduleLoader the module loader to delegate to
     * @return the delegation result
     * @throws ModuleLoadException if an error occurs
     * @deprecated Use {@link #preloadModule(String, ModuleLoader)} instead.
     */
    @Deprecated
    protected static Module preloadModule(ModuleIdentifier identifier, ModuleLoader moduleLoader) throws ModuleLoadException {
        return moduleLoader.preloadExportedModule(identifier.toString());
    }

    /**
     * Try to load a module from this module loader.  Returns {@code null} if the module is not found.  The returned
     * module may not yet be resolved.  The returned module may have a different name than the given identifier if
     * the identifier is an alias for another module.
     *
     * @param moduleIdentifier the module identifier
     * @return the module
     * @throws ModuleLoadException if an error occurs while loading the module
     * @deprecated Use {@link #loadModuleLocal(String)} instead.
     */
    @Deprecated
    protected final Module loadModuleLocal(ModuleIdentifier moduleIdentifier) throws ModuleLoadException {
        return loadModuleLocal(moduleIdentifier.toString());
    }

    /**
     * Try to load a module from this module loader.  Returns {@code null} if the module is not found.  The returned
     * module may not yet be resolved.  The returned module may have a different name than the given identifier if
     * the identifier is an alias for another module.
     *
     * @param name the module name
     * @return the module
     * @throws ModuleLoadException if an error occurs while loading the module
     */
    protected final Module loadModuleLocal(String name) throws ModuleLoadException {
        FutureModule futureModule = moduleMap.get(name);
        if (futureModule != null) {
            return futureModule.getModule();
        }

        FutureModule newFuture = new FutureModule(name);
        futureModule = moduleMap.putIfAbsent(name, newFuture);
        if (futureModule != null) {
            return futureModule.getModule();
        }

        boolean ok = false;
        try {
            final ModuleLogger log = Module.log;
            log.trace("Locally loading module %s from %s", name, this);
            final long startTime = Metrics.getCurrentCPUTime();
            final ModuleSpec moduleSpec = findModule(name);
            loadTime.addAndGet(Metrics.getCurrentCPUTime() - startTime);
            if (moduleSpec == null) {
                log.trace("Module %s not found from %s", name, this);
                return null;
            }
            if (! moduleSpec.getName().equals(name)) {
                throw new ModuleLoadException("Module loader found a module with the wrong name");
            }
            final Module module;
            if ( moduleSpec instanceof AliasModuleSpec) {
                final String aliasName = ((AliasModuleSpec) moduleSpec).getAliasName();
                try {
                    newFuture.setModule(module = loadModuleLocal(aliasName));
                } catch (RuntimeException | Error e) {
                    log.trace(e, "Failed to load module %s (alias for %s)", name, aliasName);
                    throw e;
                }
            } else {
                module = defineModule((ConcreteModuleSpec) moduleSpec, newFuture);
            }
            log.trace("Loaded module %s from %s", name, this);
            ok = true;
            return module;
        } finally {
            if (! ok) {
                newFuture.setModule(null);
                moduleMap.remove(name, newFuture);
            }
        }
    }

    /**
     * Find an already-loaded module, returning {@code null} if the module isn't currently loaded.  May block
     * while the loaded state of the module is in question (if the module is being concurrently loaded from another
     * thread, for example).
     *
     * @param identifier the module identifier
     * @return the module, or {@code null} if it wasn't found
     * @deprecated Use {@link #findLoadedModuleLocal(String)} instead.
     */
    @Deprecated
    protected final Module findLoadedModuleLocal(ModuleIdentifier identifier) {
        return findLoadedModuleLocal(identifier.toString());
    }

    /**
     * Find an already-loaded module, returning {@code null} if the module isn't currently loaded.  May block
     * while the loaded state of the module is in question (if the module is being concurrently loaded from another
     * thread, for example).
     *
     * @param name the module identifier
     * @return the module, or {@code null} if it wasn't found
     */
    protected final Module findLoadedModuleLocal(String name) {
        FutureModule futureModule = moduleMap.get(name);
        if (futureModule != null) {
            try {
                return futureModule.getModule();
            } catch (ModuleNotFoundException e) {
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * Unload a module from this module loader.  Note that this has no effect on existing modules which refer to the
     * module being unloaded.  Also, only modules from the current module loader can be unloaded.  Unloading the same
     * module more than once has no additional effect.  This method only removes the mapping for the module; any running
     * threads which are currently accessing or linked to the module will continue to function, however attempts to load
     * this module will fail until a new module is loaded with the same name.  Once this happens, if all references to
     * the previous module are not cleared, the same module may be loaded more than once, causing possible class duplication
     * and class cast exceptions if proper care is not taken.
     *
     * @param module the module to unload
     * @throws SecurityException if an attempt is made to unload a module which does not belong to this module loader
     * @throws SecurityException if the module was not defined by this module loader
     */
    protected final void unloadModuleLocal(Module module) throws SecurityException {
        final ModuleLoader moduleLoader = module.getModuleLoader();
        if (moduleLoader != this) {
            throw new SecurityException("Attempted to unload " + module + " from a different module loader");
        }
        final String id = module.getIdentifier().toString();
        final FutureModule futureModule = moduleMap.get(id);
        if (futureModule != null && futureModule.module == module) {
            moduleMap.remove(id, futureModule);
        }
    }

    /**
     * This compatibility method just calls {@code findModule(methodIdentifier.toString())} and is usually not used;
     * if however a legacy subclass overrides this method, then it will be called instead of {@link #findModule(String)},
     * and that method will redirect to this one.
     *
     * @deprecated Use {@link #findModule(String)} instead.
     *
     * @param moduleIdentifier the module identifier
     * @return the module specification
     * @throws ModuleLoadException
     */
    @Deprecated
    protected ModuleSpec findModule(final ModuleIdentifier moduleIdentifier) throws ModuleLoadException {
        if (overridesFindModuleByIdentifier) {
            return findModule0(moduleIdentifier.toString());
        } else {
            return findModule(moduleIdentifier.toString());
        }
    }

    /**
     * Find a Module's specification in this ModuleLoader by its identifier.  This can be overriden by sub-classes to
     * implement the Module loading strategy for this loader.  The default implementation iterates the module finders
{'fraction_non_alphanumeric': 0.07808147280875288, 'fraction_numerical': 0.0005854091808308773, 'mean_word_length': 2.539258412516968, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4817145', 'n_tokens_mistral': 12155, 'n_tokens_neox': 11836, 'n_words': 4287}