/* * 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.classloading.ClassLoadersHolder.ClassLoaderNotFoundException; import jetbrains.mps.classloading.ClassLoadersHolder.ClassLoadingProgress; 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.SModuleReference; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.openapi.util.ProgressMonitor; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; import java.util.stream.Collectors; /** * Note: * This class deals only with MPS-loadable modules * @see ClassLoaderManager#myMPSLoadableCondition */ class MPSClassLoadersRegistry { private static final Logger LOG = LogManager.getLogger(ClassLoadersHolder.class); private final Map<SModuleReference, ModuleClassLoader> myClassLoaders = new HashMap<SModuleReference, ModuleClassLoader>(); private final Map<SModuleReference, ClassLoadingProgress> myMPSLoadableModules = new HashMap<SModuleReference, ClassLoadingProgress>(); private final Queue<ModuleClassLoader> myDisposeQueue = new LinkedBlockingQueue<ModuleClassLoader>(); private final ClassLoadersHolder myClHolder; private final ModulesWatcher myModulesWatcher; private final SRepository myRepository; private volatile EDTDispatcher myDispatcher; public MPSClassLoadersRegistry(ClassLoadersHolder clHolder, ModulesWatcher modulesWatcher, SRepository repository, EDTDispatcher dispatcher) { myClHolder = clHolder; myModulesWatcher = modulesWatcher; myRepository = repository; myDispatcher = dispatcher; } @Nullable public ClassLoader getModuleClassLoader(@NotNull ReloadableModule module) throws ClassLoaderNotFoundException { SModuleReference mRef = module.getModuleReference(); if (!myClassLoaders.containsKey(mRef)) { throw new ClassLoaderNotFoundException(); } return myClassLoaders.get(mRef); } @NotNull public ClassLoadingProgress getClassLoadingProgress(SModuleReference mRef) { if (!myMPSLoadableModules.containsKey(mRef)) { return ClassLoadingProgress.UNLOADED; } return myMPSLoadableModules.get(mRef); } public Set<SModuleReference> doUnloadModules(Collection<SModuleReference> toUnload) { Set<SModuleReference> unloaded = new LinkedHashSet<SModuleReference>(); Collection<ModuleClassLoader> toDispose = new LinkedHashSet<ModuleClassLoader>(); for (SModuleReference mRef : toUnload) { if (!myMPSLoadableModules.containsKey(mRef)) { LOG.error("", new IllegalStateException("Module " + mRef + " is not loaded -- cannot unload")); } else { ClassLoadingProgress progress = myMPSLoadableModules.get(mRef); myMPSLoadableModules.remove(mRef); if (progress == null) { // ~ UNLOADED LOG.error("", new IllegalStateException("Module " + mRef + " must not be unloaded -- cannot unload it twice")); } else { if (progress == ClassLoadingProgress.LOADED) { if (myClassLoaders.containsKey(mRef)) { toDispose.add(myClassLoaders.get(mRef)); } else { LOG.error("", new IllegalStateException("Module " + mRef + " is loaded but has no registered ModuleClassLoader")); } } else if (progress == ClassLoadingProgress.LAZY_LOADED) { if (myClassLoaders.containsKey(mRef)) { LOG.error("", new IllegalStateException("Module " + mRef + " is lazy loaded but already has a registered ModuleClassLoader")); toDispose.add(myClassLoaders.get(mRef)); } } myClassLoaders.remove(mRef); unloaded.add(mRef); } } } myDisposeQueue.addAll(toDispose); return unloaded; } public Set<ReloadableModule> onLazyLoaded(Collection<ReloadableModule> toLoadLazy) { Set<ReloadableModule> lazyLoaded = new LinkedHashSet<ReloadableModule>(); for (ReloadableModule module : toLoadLazy) { SModuleReference mRef = module.getModuleReference(); ClassLoadingProgress classLoadingProgress = myMPSLoadableModules.get(mRef); if (classLoadingProgress != null) { LOG.error("Illegal state: module is already loaded " + module, new Throwable()); } else { myMPSLoadableModules.put(mRef, ClassLoadingProgress.LAZY_LOADED); lazyLoaded.add(module); } } return lazyLoaded; } public void doLoadModules(final Collection<? extends ReloadableModule> toLoad) { final List<ModuleClassLoader> moduleClassLoaders = createModuleCLs(toLoad); for (ModuleClassLoader classLoader : moduleClassLoaders) { SModuleReference moduleReference = classLoader.getModule().getModuleReference(); ClassLoadingProgress progress = getClassLoadingProgress(moduleReference); if (progress == ClassLoadingProgress.UNLOADED) { throw new IllegalStateException("Module " + moduleReference + " is in UNLOADED state, i.e. the class loading clients know nothing about this module"); } else if (progress == ClassLoadingProgress.LAZY_LOADED) { putClassLoader(moduleReference, classLoader); onLoaded(moduleReference); } } } @NotNull private List<ModuleClassLoader> createModuleCLs(final Collection<? extends ReloadableModule> toLoad) { final List<ModuleClassLoader> moduleClassLoaders = new ArrayList<ModuleClassLoader>(); for (ReloadableModule module : toLoad) { ModuleClassLoader moduleClassLoader = createModuleClassLoader(module); moduleClassLoaders.add(moduleClassLoader); } return moduleClassLoaders; } private ModuleClassLoader createModuleClassLoader(@NotNull ReloadableModule module) { myRepository.getModelAccess().checkReadAccess(); // need for new ModuleClassLoader() LOG.debug("Creating ModuleClassLoader for " + module); Collection<ReloadableModule> deps = myModulesWatcher.getResolvedDependencies(Collections.singletonList(module)); final ModuleClassLoaderSupport support = ModuleClassLoaderSupport.create(module, () -> deps.stream() .map(myClHolder::getClassLoader) .distinct() .collect(Collectors.toList())); return new ModuleClassLoader(support); } private void onLoaded(SModuleReference module) { assert myClassLoaders.containsKey(module); ClassLoadingProgress classLoadingProgress = myMPSLoadableModules.get(module); if (classLoadingProgress != ClassLoadingProgress.LAZY_LOADED) { LOG.error("Illegal state: module has not been lazy loaded " + module, new Throwable()); } myMPSLoadableModules.put(module, ClassLoadingProgress.LOADED); } private void putClassLoader(SModuleReference module, ModuleClassLoader classLoader) { myClassLoaders.put(module, classLoader); } /** * Very quick action. * We do it in EDT asynchronously, because there are some class loading clients which eager to dispose asynchronously * Double invokeAndLater because we need to allow EDT activities under progress [AP] */ public void flushDisposeQueue() { if (myDisposeQueue.isEmpty()) return; final List<ModuleClassLoader> toDispose = new ArrayList<ModuleClassLoader>(myDisposeQueue); myDispatcher.invokeInEDT(() -> { LOG.debug("Disposing " + toDispose.size() + " class loaders"); for (ModuleClassLoader classLoader : toDispose) { classLoader.dispose(); } }); myDisposeQueue.clear(); } public void dispose() { if (!myDisposeQueue.isEmpty()) { flushDisposeQueue(); } } public void setDispatcher(@NotNull EDTDispatcher dispatcher) { myDispatcher = dispatcher; } }