/*
 * 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.components.CoreComponent;
import jetbrains.mps.internal.collections.runtime.IterableUtils;
import jetbrains.mps.module.ReloadableModule;
import jetbrains.mps.progress.EmptyProgressMonitor;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.smodel.tempmodel.TempModule;
import jetbrains.mps.util.Computable;
import jetbrains.mps.util.NotCondition;
import jetbrains.mps.util.annotation.ToRemove;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.mps.annotations.Internal;
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.util.ProgressMonitor;
import org.jetbrains.mps.openapi.util.SubProgressKind;
import org.jetbrains.mps.util.Condition;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

import static jetbrains.mps.classloading.ClassLoadersHolder.ClassLoadingProgress.LOADED;
import static jetbrains.mps.classloading.ClassLoadersHolder.ClassLoadingProgress.UNLOADED;

/**
 * A ClassLoaderManager is a singleton and provides an internal API for loading classes
 * within MPS.
 * NOTE: External API is placed in {@link jetbrains.mps.module.ReloadableModule} interface.
 * Using the methods of this class is not recommended.
 *
 * In order to get Class from a module call {@link #getClass} method.
 * [Note: the module needs to be loadable. {@link #canLoad} must return true]
 * @see #myLoadableCondition
 *
 * General information:
 * A MPS java module is loadable iff it is possible to associate some ClassLoader with it.
 * Currently there are two types of <it>loadable</it> modules:
 * 1. <it>Reloadable</it> modules are modules which ClassLoader maybe redeployed on-the-fly
 * @see jetbrains.mps.module.ReloadableModule
 * @see #myMPSLoadableCondition
 * Presently the associated ClassLoader for these modules is {@link ModuleClassLoader}.
 * ClassLoaderManager stores a map of this ClassLoader instances, reloads them if needed, delegates class requests to them.
 * 2. <it>Non-reloadable</it> modules are not reloadable modules.
 * Currently such modules are bundled with Idea plugin, the associated ClassLoader for these modules is the result
 * of the method {@link com.intellij.openapi.extensions.PluginDescriptor#getPluginClassLoader()} call.
 * CLManager delegates Class/ClassLoader requests to Idea plugin [for these modules].
 *
 * Common part
 * CLManager listens to newly added <it>loadable</it> modules (into the repository) and to modules' removal.
 * When module is added, CLManager marks it as ({@link LAZY_LOADED}) and broadcasts the event to
 * {@link jetbrains.mps.classloading.MPSClassesListener} clients.
 * When module's classes (or ClassLoader) are requested, the actual module load happens.
 * When module is removed from the repository, CLManager unloaded module's data from its' storage.
 * @see jetbrains.mps.classloading.ClassLoadersHolder.ClassLoadingProgress for more information on module's loading progress and module's lifecycle
 *
 * Every module add/remove/reload triggers events dispatching to MPSClassesListeners
 * @see jetbrains.mps.classloading.MPSClassesListener
 *
 * Also CLManager tracks the <em>validity</em> of the repository modules.
 * The invariant condition is that a module can not be (class) loaded if any of its dependencies is absent in the repository.
 * That means that for an <em>invalid</em> module CLManager will return <code>null</code> for all Class/ClassLoader requests (#getClass, #getClassLoader)
 * @see jetbrains.mps.classloading.ModulesWatcher
 * @see jetbrains.mps.classloading.ModulesWatcher.ClassLoadingStatus
 * @see jetbrains.mps.classloading.ModulesWatcher#getStatus(SModuleReference)
 *
 * Reloadable part
 * Any <it>reloadable</it> module M has a class loading lifecycle like this:
 * # module M is added to the repository [no ModuleClassLoader created]
 * # someone reloads M or asks for the classloader of M -> initializing ModuleClassLoader creation for M (lazy load) [ModuleClassLoader is constructed]
 * # more M reloads and class requests
 * # module M is removed from the repository -> ModuleClassLoader gets disposed. [ModuleClassLoader removed]
 *
 * Reload may be triggered by a client explicitly with {@link #reloadModules(Iterable)}.
 * [**] Notice that it is a very uncommon case when you might need an explicit reload.
 * Currently a module's reload happens automatically on module's changes (some specific changes, details below).
 * @see BatchEventsProcessor for details
 *
 * CLManager exploits a lazy mechanism of module's reloading. It stacks all module events,
 * and occasionally <em>refresh</em> happens: CLM flushes them and processes all the accumulated events.
 * When CLManager refreshes its state becomes actual. [in the sense of information about modules', their class loading status and their ClassLoader's]
 * The state IS guaranteed to be actual at these points:
 * 1. Right after the end of write action [CLManager has a {@link org.jetbrains.mps.openapi.repository.WriteActionListener}]
 * 2. In the middle of write action if a Class or ClassLoader was requested [{@link #getClassLoader}]. Only write action holder is able to provoke <em>refresh</em> [!]
 * 3. Explicit reload: a call of reload methods [{@link #reloadModule}, {@link #reloadModules}].
 * @see #getClassLoader(org.jetbrains.mps.openapi.module.SModule) documentation for more details on pt. 2
 * @see #refresh()
 *
 * Repository lock policy
 * Every reload requires a repository write lock. Actual ModuleClassLoader construction happens inside the read action,
 * @see #doLoadModules(Iterable, ProgressMonitor)
 *
 *
 * FIXME logic here must be rewritten in a more abstract way to allow both lazy and non-lazy implementations
 * FIXME the module dependecy tracking must be isolated from the class loading logic
 *
 * TODO the workflow between ModuleEventsHandler, ClassLoaderManager and ModulesWatcher is too complicated and impossible to perceive, it needs to be done over again
 */
@SuppressWarnings("unchecked")
public class ClassLoaderManager implements CoreComponent {
  private static final Logger LOG = LogManager.getLogger(ClassLoaderManager.class);

  private static ClassLoaderManager INSTANCE;

  private final Object myLoadingModulesLock = new Object();

  /**
   * @deprecated use {@link MPSCoreComponents#getClassLoaderManager} instead
   */
  @Deprecated
  public static ClassLoaderManager getInstance() {
    return INSTANCE;
  }

  /**
   * SRepository must possess an instance of CLManager. No singletons!
   * CLManager will listen for SRepository events.
   */
  @ToRemove(version = 3.2)
  private final SRepository myRepository;

  private final ClassLoadersHolder myClassLoadersHolder;

  private final ModulesWatcher myModulesWatcher;

  private final ClassLoadingBroadCaster myBroadCaster;

  private final ModuleEventsHandler myRepositoryListener;

  public ClassLoaderManager(@NotNull SRepository repository) {
    this(repository, new DefaultEDTDispatcher());
  }

  ClassLoaderManager(@NotNull SRepository repository, @NotNull EDTDispatcher dispatcher) {
    myRepository = repository;
    myModulesWatcher = new ModulesWatcher(myRepository, myWatchableCondition);
    myClassLoadersHolder = new ClassLoadersHolder(myRepository, myModulesWatcher, dispatcher);
    myRepositoryListener = new ModuleEventsHandler(repository, myModulesWatcher);
    myBroadCaster = new ClassLoadingBroadCaster(repository.getModelAccess());
  }

  @Deprecated
  @ToRemove(version = 3.4)
  public void setDispatcher(@NotNull EDTDispatcher dispatcher) {
    myClassLoadersHolder.setDispatcher(dispatcher);
  }

  @Override
  public void init() {
    myRepository.getModelAccess().checkWriteAccess();

    if (INSTANCE != null) throw new IllegalStateException("ClassLoaderManager is already initialized");
    INSTANCE = this;
    myRepositoryListener.init(this);
    myClassLoadersHolder.init();
  }

  @Override
  public void dispose() {
    myRepository.getModelAccess().checkWriteAccess();

    myClassLoadersHolder.dispose();
    myRepositoryListener.dispose();
    INSTANCE = null;
  }

  @TestOnly
  ModulesWatcher getModulesWatcher() {
    return myModulesWatcher;
  }

  /**
   * please do not use : it breaks the internal {@link ModulesWatcher} consistency (such request triggers unexpected refresh which is not right)
   * TODO the dependency tracking logic will be extracted into the independent subsystem
   */
  @ToRemove(version = 3.3)
  @Deprecated
  public boolean isValidForClassloading(SModuleReference m){
    return myModulesWatcher.getStatus(m).isValid();
  }

  /**
   * Please ensure this method returns true before calling {@link #getClass} or {@link #getClassLoader} methods
   * @return true whenever module can be associated with some class loader.
   * [Currently it can be either MPS ModuleClassLoader or Idea PluginClassLoader]
   *
   * @deprecated it is better to check whether the module is an instance of ReloadableModule and use {@link jetbrains.mps.module.ReloadableModule} interface API
   */
  @Deprecated
  @ToRemove(version = 3.2)
  public boolean canLoad(@NotNull SModule module) {
    return module instanceof ReloadableModule;
  }

  private void assertCanLoad(@NotNull SModule module) {
    if (!canLoad(module)) {
      throw new IllegalArgumentException("Classes of the module " + module.getModuleName() + " are unavailable within the MPS class loading system");
    }
  }

  /**
   * TODO refactor all usages of getClass()
   * Contract: @param module must be loadable ({@link #myLoadableCondition}
   * So if {@link #canLoad} method returns true you will get your class
   *
   * @deprecated use module-specific methods which throw different ClassNotFoundExceptions,
   * you need to process it on your own (probably show some user notification)
   *
   * @see jetbrains.mps.module.ReloadableModule
   * @see ModuleIsNotLoadableException
   * @see ModuleClassNotFoundException
   */
  @Deprecated
  @ToRemove(version = 3.2)
  @Nullable
  public Class<?> getClass(@NotNull SModule module, String classFqName) {
    assertCanLoad(module);
    try {
      return ((ReloadableModule) module).getClass(classFqName);
    } catch (ModuleIsNotLoadableException e) {
      LOG.error("Exception during class loading. Probably one of the solutions has no solution kind set or lacks in Idea plugin facet.", e);
    } catch (ClassNotFoundException e) {
      LOG.error("Exception during class loading", e);
    }
    return null;
  }

  /**
   * TODO refactor all usages of getOwnClass()
   * @deprecated use module-specific methods which throw different ClassNotFoundExceptions,
   * you need to process it by yourself (probably show some user notification)
   * @see jetbrains.mps.module.ReloadableModule
   * @see ModuleIsNotLoadableException
   */
  @Deprecated
  @ToRemove(version = 3.2)
  @Nullable
  public Class<?> getOwnClass(@NotNull SModule module, String classFqName) {
    assertCanLoad(module);
    try {
      return ((ReloadableModule) module).getOwnClass(classFqName);
    } catch (ModuleIsNotLoadableException e) {
      LOG.warn("Exception during class loading. Probably one of the solutions has no solution kind set or lacks in Idea plugin facet.", e);
    } catch (ClassNotFoundException ignored) {
    }
    return null;
  }

  /**
   * @return the class loader associated with the module. Note that sometimes it may return "outdated" ClassLoader.
   * To be exact it returns the classloader for module, which was actual for the moment of last refresh event.
   * @see #refresh()
   *
   * Refresh happens at these points:
   * 1. At the end of write action,
   * 2. During #getClassLoader calls inside the write action,
   * 3. Also it may be triggered explicitly by #reloadModules(), #reloadModule(), etc. call.
   *
   * This method can return the class loader of the IDEA plugin which manages the module's classes.
   * Use it if you want to get a class from the module with IdeaPluginFacet.
   *
   * @deprecated use module-specific methods which throw ClassNotFoundException,
   * you need to process it by yourself (probably show some user notification)
   *
   * @see ModuleIsNotLoadableException
   * @see jetbrains.mps.module.ReloadableModule
   */
  @Deprecated
  @Nullable
  public ClassLoader getClassLoader(final SModule module) {
    if (!myLoadableCondition.met(module)) {
      return null;
    }

    if (myRepository.getModelAccess().canWrite()) {
      refresh();
    }
    ReloadableModule reloadableModule = (ReloadableModule) module;
    if (!myValidCondition.met(reloadableModule)) {
      return null;
    }
    doLoadModules(Collections.singleton(reloadableModule), new EmptyProgressMonitor());
    return doGetClassLoader(reloadableModule);
  }

  @Nullable
  private ClassLoader doGetClassLoader(@NotNull ReloadableModule module) {
    return myClassLoadersHolder.getClassLoader(module);
  }

  private boolean canCreate(@NotNull ReloadableModule module) {
    return ModuleClassLoaderSupport.canCreate(module);
  }

  /**
   * Flushes all delayed notifications to keep up with the module repository state
   * @see ModuleEventsHandler
   * @return if refresh actually happened
   */
  private boolean refresh() {
    checkWriteAccess();
    return myRepositoryListener.refresh();
  }

  /**
   * @lazy
   * @param modules are modules which are about to load. The notifications for {@link MPSClassesListener} are sent here.
   * The actual load happens in {@link #doLoadModules} on a method call of {@link #getClassLoader}.
   *
   * Note: currently we need to broadcast load/unload events because there are clients of {@link MPSClassesListener}
   * These clients need to be rewritten in a lazy way, i.e. using only #getClass [#getClassLoader] method.
   * @deprecated there is an intention to get rid of {@link MPSClassesListener} clients. When it's done we are able to remove this method.
   */
  Collection<ReloadableModule> preLoadModules(Iterable<? extends ReloadableModule> modules, ProgressMonitor monitor) {
    checkWriteAccess();
    monitor.start("Loading", 6);
    try {
      Set<ReloadableModule> modulesPreLoad = filterModules(modules, myValidCondition);
      if (modulesPreLoad.isEmpty()) return Collections.emptySet();

      // transitive closure
      modulesPreLoad.addAll(myModulesWatcher.getResolvedDependencies(modulesPreLoad));
      modulesPreLoad = filterModules(modulesPreLoad, myUnloadedCondition, myValidCondition);
      if (modulesPreLoad.isEmpty()) return Collections.emptySet();
      monitor.advance(1);

      // add valid back dependencies too; [if now (with new modules) they are fine to load]
      modulesPreLoad.addAll(myModulesWatcher.getResolvedBackDependencies(modulesPreLoad));
      modulesPreLoad = filterModules(modulesPreLoad, myUnloadedCondition, myMPSLoadableCondition, myValidCondition);
      if (modulesPreLoad.isEmpty()) return Collections.emptySet();

      Set<ReloadableModule> modulesToNotify = myClassLoadersHolder.onLazyLoaded(modulesPreLoad);
      myBroadCaster.onLoad(modulesToNotify, monitor.subTask(5, SubProgressKind.AS_COMMENT));

      return modulesToNotify;
    } finally {
      monitor.done();
    }
  }

  /**
   * hack for 3.4
   */
  @Deprecated
  @ToRemove(version = 2018.1)
  @Internal
  public synchronized void runNonReloadableTransaction(Runnable runnable) {
    try {
      myRepositoryListener.pause();
      runnable.run();
    } finally {
      myRepositoryListener.proceed();
    }
  }

  /**
   * Creates ModuleClassLoader for those modules which are MPS-loadable and valid
   *
   * @see #myMPSLoadableCondition
   * @see #myValidCondition
   */
  @NotNull
  private Collection<ReloadableModule> doLoadModules(final Iterable<? extends ReloadableModule> modules, final ProgressMonitor monitor) {
    monitor.start("Loading", 1);
    try {
      return new ModelAccessHelper(myRepository).runReadAction((Computable<Collection<ReloadableModule>>) () -> {
        synchronized (myLoadingModulesLock) { // provides synchronization only in this block
          Set<ReloadableModule> modulesToLoad = new LinkedHashSet<ReloadableModule>(filterModules(modules, myWatchableCondition, myValidCondition));
          if (modulesToLoad.isEmpty()) return Collections.emptySet();

          // transitive closure
          modulesToLoad.addAll(myModulesWatcher.getResolvedDependencies(modulesToLoad));
          modulesToLoad = filterModules(modulesToLoad, myMPSLoadableCondition, myNotLoadedCondition);
          if (modulesToLoad.isEmpty()) return Collections.emptySet();

          LOG.debug("Loading " + modulesToLoad.size() + " modules");
          monitor.advance(1);
          if (!filterModules(modulesToLoad, myUnloadedCondition).isEmpty()) {
            LOG.warn("Some modules are not preloaded yet : cannot load them");
          }
          myClassLoadersHolder.doLoadModules(modulesToLoad);
          return modulesToLoad;
        }
      });
    } finally {
      monitor.done();
    }
  }

  /**
   * Stops tracking all the {@code modules}, which are MPS-loadable
   * Disposes all class loaders for these modules
   * Method is not lazy
   *
   * @see #myMPSLoadableCondition
   */
  @NotNull
  Collection<ReloadableModule> unloadModules(Iterable<? extends SModuleReference> modules, @NotNull ProgressMonitor monitor) {
    checkWriteAccess();
    monitor.start("Unloading", 6);
    try {
      Condition<SModuleReference> loadedCondition = new NotCondition<SModuleReference>(myUnloadedRefCondition);
      Set<SModuleReference> modulesToUnload = filterModules(modules, loadedCondition);
      if (modulesToUnload.isEmpty()) return Collections.emptySet();

      // transitive closure
      Collection<? extends SModuleReference> modulesAndBackDeps = myModulesWatcher.getBackDependencies(modulesToUnload);
      modulesToUnload = filterModules(modulesAndBackDeps, loadedCondition);
      if (modulesToUnload.isEmpty()) return Collections.emptySet();

      LOG.debug("Unloading " + modulesToUnload.size() + " modules");
      Collection<ReloadableModule> unloadedModules = myBroadCaster.onUnload(modulesToUnload, monitor.subTask(5, SubProgressKind.AS_COMMENT));
      myClassLoadersHolder.doUnloadModules(modulesToUnload);

      return unloadedModules;
    } finally {
      monitor.done();
    }
  }

  static <M> Set<M> filterModules(Iterable<? extends M> modules, Condition<M>... conditions) {
    CompositeCondition<M> compositeCondition = new CompositeCondition<M>(conditions);
    Set<M> filteredModules = new LinkedHashSet<M>();
    for (M module : modules) {
      if (compositeCondition.met(module)) filteredModules.add(module);
    }
    return filteredModules;
  }

  /**
   * @deprecated It is recommended to use {@link jetbrains.mps.classloading.DeployListener}
   */
  @Deprecated
  public void addClassesHandler(MPSClassesListener handler) {
    myBroadCaster.addClassesHandler(handler);
  }

  @Deprecated
  public void removeClassesHandler(MPSClassesListener handler) {
    myBroadCaster.removeClassesHandler(handler);
  }

  /**
   * @deprecated It is recommended to use {@link jetbrains.mps.classloading.DeployListener}
   */
  @Deprecated
  public void addReloadListener(ModuleReloadListener listener) {
    myBroadCaster.addReloadListener(listener);
  }

  public void addListener(@NotNull DeployListener listener) {
    myBroadCaster.addListener(listener);
  }

  public void removeListener(@NotNull DeployListener listener) {
    myBroadCaster.removeListener(listener);
  }

  public void removeReloadListener(ModuleReloadListener listener) {
    myBroadCaster.removeReloadListener(listener);
  }
  /**
   * Use this method to invalidate modules (namely, recreate their class loaders)
   * There are also useful {@link #reloadModules(Iterable)} and {@link #reloadModule(SModule)}.
   * @deprecated the module is reloaded automatically on events like moduleChanged, dependenciesChanged, etc.
   * [!] no need to call this method directly anymore
   *
   * TODO: add listening to class files updating and remove explicit call from ModuleMaker and the others
   * FIXME: remove TempModule: it should not be processed by CLManager. It maintains only repository modules!
   */
  @Deprecated
  public void reloadModules(Iterable<? extends SModule> modules, @NotNull ProgressMonitor monitor) {
    checkWriteAccess();
    refresh();
    doReloadModules(modules, monitor);
  }

  Collection<ReloadableModule> doReloadModules(Iterable<? extends SModule> modules, @NotNull ProgressMonitor monitor) {
    checkWriteAccess();
    if (IterableUtils.isEmpty(modules)) {
      LOG.info("Reloaded 0 modules");
      return new ArrayList();
    }
    try {
      long beginTime = System.nanoTime();
      monitor.start("Reloading Modules", 2);
      boolean silentMode = true;
      for (SModule module : modules) {
        if (!(module instanceof TempModule)) {
          silentMode = false;
          break;
        }
      }
      Collection<ReloadableModule> modulesToReload = new LinkedHashSet();
      for (SModule module : modules) {
        if (!(module instanceof TempModule) && module.getRepository() == null) {
          throw new IllegalStateException(String.format("Cannot reload the module %s which does not belong to a repository", module));
        }
        if (module instanceof ReloadableModule) {
          modulesToReload.add((ReloadableModule) module);
        }
      }
      if (modulesToReload.isEmpty()) return Collections.emptySet();

      myModulesWatcher.updateModules(modulesToReload);
      Collection<? extends ReloadableModule> unloadedModules = unloadModules(myModulesWatcher.getModuleRefs(modulesToReload), monitor.subTask(1));
      modulesToReload.addAll(unloadedModules);
      Collection<ReloadableModule> loadedModules = preLoadModules(modulesToReload, monitor.subTask(1));
      myBroadCaster.onReload(loadedModules);

      if (!silentMode) {
        LOG.info(String.format("Reloaded %d module(s) in %.3f s", loadedModules.size(), (System.nanoTime() - beginTime) / 1e9));
      }

      return new LinkedHashSet<ReloadableModule>(loadedModules);
    } finally {
      myClassLoadersHolder.scheduleClassLoaderDisposeInEDT();
      monitor.done();
    }
  }

  /**
   * @deprecated
   * @see #reloadModules(Iterable, org.jetbrains.mps.openapi.util.ProgressMonitor)
   */
  @Deprecated
  public void reloadModules(Iterable<? extends SModule> modules) {
    reloadModules(modules, new EmptyProgressMonitor());
  }

  /**
   * @deprecated use module-specific method {@link jetbrains.mps.module.ReloadableModule#reload()}
   * @see jetbrains.mps.module.ReloadableModule
   */
  @Deprecated
  @ToRemove(version = 3.2)
  public void reloadModule(SModule module) {
    reloadModules(Collections.singleton(module), new EmptyProgressMonitor());
  }

  /**
   * Note: usually reloading only the "dirty" modules is enough.
   * Please take a look at {@link #reloadModule} and {@link #reloadModules} methods.
   * @deprecated
   * @see #reloadModules(Iterable, org.jetbrains.mps.openapi.util.ProgressMonitor)
   */
  public void reloadAll(@NotNull ProgressMonitor monitor) {
    reloadModules(myRepository.getModules(), monitor);
  }

  private void checkWriteAccess() {
    myRepository.getModelAccess().checkWriteAccess();
  }

  // conditions part
  private static class CompositeCondition<T> implements Condition<T> {
    private final Condition<T>[] myConditions;

    public CompositeCondition(Condition<T>... conditions) {
      myConditions = conditions;
    }

    @Override
    public boolean met(T t) {
      for (Condition<T> condition : myConditions) {
        if (!condition.met(t)) return false;
      }
      return true;
    }
  }

  /**
   * it is possible to associate a ClassLoader with such module
   */
  private final Condition<SModule> myLoadableCondition = new Condition<SModule>() {
    @Override
    public boolean met(SModule module) {
      return canLoad(module);
    }
  };

  /**
   * the modules which we want to watch (and trace the dependencies between them)
   */
  private final Condition<ReloadableModule> myWatchableCondition = new Condition<ReloadableModule>() {
    @Override
    public boolean met(ReloadableModule module) {
      return true;
    }
  };

  public boolean isLoadedByMPS(@NotNull ReloadableModule module) {
    return myMPSLoadableCondition.met(module);
  }

  /**
   * it is possible to create ModuleClassLoader for such module
   */
  private final Condition<ReloadableModule> myMPSLoadableCondition = new Condition<ReloadableModule>() {
    @Override
    public boolean met(ReloadableModule module) {
      return canCreate(module);
    }
  };

  /**
   * status of this module is valid in the dependencies graph
   * @see ModulesWatcher
   */
  private final Condition<ReloadableModule> myValidCondition = new Condition<ReloadableModule>() {
    @Override
    public boolean met(ReloadableModule module) {
      SModuleReference mRef = module.getModuleReference();
      return myWatchableCondition.met(module) && myModulesWatcher.getStatus(mRef).isValid();
    }
  };

  private final Condition<ReloadableModule> myUnloadedCondition = new Condition<ReloadableModule>() {
    @Override
    public boolean met(ReloadableModule module) {
      return myClassLoadersHolder.getClassLoadingProgress(module.getModuleReference()) == UNLOADED;
    }
  };

  private final Condition<SModuleReference> myUnloadedRefCondition = new Condition<SModuleReference>() {
    @Override
    public boolean met(SModuleReference mRef) {
      return myClassLoadersHolder.getClassLoadingProgress(mRef) == UNLOADED;
    }
  };

  private final Condition<ReloadableModule> myLoadedCondition = new Condition<ReloadableModule>() {
    @Override
    public boolean met(ReloadableModule module) {
      return myClassLoadersHolder.getClassLoadingProgress(module.getModuleReference()) == LOADED;
    }
  };

  private final Condition<ReloadableModule> myNotLoadedCondition = new NotCondition<ReloadableModule>(myLoadedCondition);
}
