/*
* Copyright 2003-2015 JetBrains s.r.o.
*
* 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 jetbrains.mps.classloading;
import jetbrains.mps.module.ReloadableModule;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SModuleReference;
import org.jetbrains.mps.openapi.module.SRepository;
import org.jetbrains.mps.openapi.module.SRepositoryListener;
import org.jetbrains.mps.openapi.module.SRepositoryListenerBase;
import org.jetbrains.mps.openapi.util.ProgressMonitor;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* This class stores a map SModuleReference->ModuleClassLoader
*
* Note: the actual dispose of ModuleClassLoaders happen asynchronously in the EDT
* @see MPSClassLoadersRegistry#flushDisposeQueue()
*
* @see ClassLoaderManager#myLoadableCondition
*/
public class ClassLoadersHolder {
private static final Logger LOG = LogManager.getLogger(ClassLoadersHolder.class);
private static final List<String> INTERNAL_EXCLUDES = Arrays.asList("jetbrains.mps.samples.xmlPersistence", "TestBehaviorReflective");
private final MPSClassLoadersRegistry myCLRegistry;
private final SRepositoryListener myRepositoryListener = new SRepositoryListenerBase() {
@Override
public void moduleAdded(@NotNull SModule module) {
checkPluginIsValid(module);
}
private void checkPluginIsValid(@NotNull SModule module) {
CustomClassLoadingFacet customClassLoadingFacet = module.getFacet(CustomClassLoadingFacet.class);
if (customClassLoadingFacet != null) {
if (!customClassLoadingFacet.isValid() && !INTERNAL_EXCLUDES.contains(module.getModuleName())) {
LOG.warn("Facet of the module " + module + " is not valid --" +
" possibly the provided idea plugin (in the properties dialog/idea plugin facet tab) cannot be found among the bundled plugins");
}
}
}
};
private final SRepository myRepository;
public ClassLoadersHolder(SRepository repository, ModulesWatcher modulesWatcher, EDTDispatcher dispatcher) {
myRepository = repository;
myCLRegistry = new MPSClassLoadersRegistry(this, modulesWatcher, repository, dispatcher);
}
public void init() {
myRepository.addRepositoryListener(myRepositoryListener);
}
public void dispose() {
myCLRegistry.dispose();
myRepository.removeRepositoryListener(myRepositoryListener);
}
@Nullable
public ClassLoader getClassLoader(ReloadableModule module) {
try {
return getModuleClassLoader(module);
} catch (ClassLoaderNotFoundException ignored) {
// do nothing, there is no MPS ModuleClassLoader for this module
}
try {
return getNonReloadableClassLoader(module);
} catch (ClassLoaderNotFoundException ignored) {
// do nothing, there is no IDEA ClassLoader for this module
}
return null;
}
@Nullable
private ClassLoader getNonReloadableClassLoader(SModule module) throws ClassLoaderNotFoundException {
CustomClassLoadingFacet customClassLoadingFacet = module.getFacet(CustomClassLoadingFacet.class);
if (customClassLoadingFacet != null) {
if (customClassLoadingFacet.isValid()) {
return customClassLoadingFacet.getClassLoader();
} else {
return null;
}
}
throw new ClassLoaderNotFoundException();
}
@Nullable
private ClassLoader getModuleClassLoader(ReloadableModule module) throws ClassLoaderNotFoundException {
return myCLRegistry.getModuleClassLoader(module);
}
/**
* @return {@link ClassLoadingProgress} for the module. See the documentation of
* {@link ClassLoadingProgress} for the description of states and a typical lifecycle of module in a repository.
*/
@NotNull
public ClassLoadingProgress getClassLoadingProgress(SModuleReference mRef) {
return myCLRegistry.getClassLoadingProgress(mRef);
}
public void scheduleClassLoaderDisposeInEDT() {
LOG.debug("Scheduling ModuleClassLoader disposal");
myCLRegistry.flushDisposeQueue();
}
/**
* @param toUnload for these modules ModuleClassLoaders were disposed
* @return modules which changed their ClassLoadingProgress from LAZY_LOADED or LOADED to UNLOADED.
*/
public Set<SModuleReference> doUnloadModules(Set<SModuleReference> toUnload) {
return myCLRegistry.doUnloadModules(toUnload);
}
/**
* @param toLoadLazy for these modules only notifications {@link MPSClassesListener#afterClassesLoaded} were sent,
* so for {@link MPSClassesListener} clients these modules appear to be loaded.
* No actual loading is performed for these modules.
* @return modules which changed their ClassLoadingProgress from UNLOADED to LAZY_LOADED.
*/
public Set<ReloadableModule> onLazyLoaded(Set<ReloadableModule> toLoadLazy) {
return myCLRegistry.onLazyLoaded(toLoadLazy);
}
/**
* @param toLoad for these modules ModuleClassLoaders were actually created
*/
public void doLoadModules(Set<ReloadableModule> toLoad) {
myCLRegistry.doLoadModules(toLoad);
}
public void setDispatcher(@NotNull EDTDispatcher dispatcher) {
myCLRegistry.setDispatcher(dispatcher);
}
/**
* Class loading progress of each MPS-loadable module.
*
* Module lifecycle:
* At first the module is UNLOADED. It comes to repository and a call of {@link ClassLoaderManager#preLoadModules(Iterable, org.jetbrains.mps.openapi.util.ProgressMonitor)} happens.
* Then we check whether the module's dependencies are valid to load (and some other conditions). If everything is okay then we send
* broadcast notification to the clients of {@link jetbrains.mps.classloading.MPSClassesListener}.
* The state of module is changed to LAZY_LOADED at that moment.
* When the classes of module are requested [through #getClass(), #getOwnClass(), #getClassLoader()] methods,
* the actual ClassLoader construction happens and then the module is marked as LOADED.
* LAZY_LOADED state could not be skipped.
*
* When #reloadModules happens, module ClassLoader's are unloaded and then preLoaded (!) again. [back to lazy state again]
*
* So the state diagram looks like this:
* UNLOADED -> LAZY_LOADED -> LOADED
* LAZY_LOADED -> UNLOADED
* LOADED -> UNLOADED
*/
public enum ClassLoadingProgress {
/**
* Class loading has not been initiated yet. [Implies there is no such module in the repository].
* Note: this enum value is not stored in corresponding map for the sake of simplicity.
*/
UNLOADED,
/**
* The notifications for {@link MPSClassesListener} clients were sent. No actual class loading happened,
* This module was only marked to load.
*/
LAZY_LOADED,
/**
* ModuleClassLoader's are created.
*/
LOADED
}
static class ClassLoaderNotFoundException extends Exception {}
}