/* * Copyright 2003-2014 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 jetbrains.mps.module.ReloadableModuleBase; import jetbrains.mps.progress.EmptyProgressMonitor; import jetbrains.mps.smodel.SRepositoryBatchListener; import org.jetbrains.annotations.NotNull; 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.event.SModuleAddedEvent; import org.jetbrains.mps.openapi.module.event.SModuleChangedEvent; import org.jetbrains.mps.openapi.module.event.SModuleEventVisitor; import org.jetbrains.mps.openapi.module.event.SModuleRemovedEvent; import org.jetbrains.mps.openapi.module.event.SModuleRemovingEvent; import org.jetbrains.mps.openapi.module.event.SRepositoryEvent; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; /** * We batch all events during write actions. (modules' events like module adding/removing/changing) * @see BatchEventsProcessor * * We flush the events both at the end of write action and on request (triggered by #getClass, #getClassLoader or #reloadModules) * No such module events can happen outside of write action. */ class ModuleEventsHandler implements SRepositoryBatchListener { private ClassLoaderManager myManager; private final ModulesWatcher myModulesWatcher; private final ModuleEventsDispatcher myDispatcher; // order for modules loading in order to reproduce any error private static final Comparator<Object> MODULE_COMPARATOR = new Comparator<Object>() { @Override public int compare(Object m1, Object m2) { return m1.toString().compareTo(m2.toString()); } }; public ModuleEventsHandler(@NotNull SRepository repository, ModulesWatcher modulesWatcher) { myModulesWatcher = modulesWatcher; myDispatcher = new ModuleEventsDispatcher(repository); } public void init(ClassLoaderManager classLoaderManager) { myManager = classLoaderManager; myDispatcher.init(); myDispatcher.addRepositoryBatchEventsListener(this); } public void dispose() { myDispatcher.removeRepositoryBatchEventsListener(this); myDispatcher.dispose(); } /** * flushes all the events to get the actual state in the repository * @return true if refresh happened */ boolean refresh() { return myDispatcher.flush(); } private void addModules(List<? extends ReloadableModuleBase> modules) { Collections.sort(modules, MODULE_COMPARATOR); myModulesWatcher.addModules(modules); myManager.preLoadModules(modules, new EmptyProgressMonitor()); } private void updateModules(List<? extends ReloadableModuleBase> modules) { List<SModule> modulesToReload = new ArrayList<SModule>(); for (ReloadableModuleBase module : modules) modulesToReload.add(module); Collections.sort(modulesToReload, MODULE_COMPARATOR); myManager.doReloadModules(modulesToReload, new EmptyProgressMonitor()); } private void removeModules(List<SModuleReference> modules) { Collections.sort(modules, MODULE_COMPARATOR); myManager.unloadModules(modules, new EmptyProgressMonitor()); myModulesWatcher.removeModules(modules); } @Override public void eventsHappened(List<SRepositoryEvent> events) { if (events.size() == 0) return; MyModuleEventVisitor visitor = new MyModuleEventVisitor(); for (SRepositoryEvent event : events) { event.accept(visitor); } List<SModuleReference> modulesToUnload = visitor.getModulesToUnload(); List<ReloadableModuleBase> modulesToUpdate = visitor.getModulesToUpdate(); List<ReloadableModuleBase> modulesToLoad = visitor.getModulesToLoad(); if (modulesToUnload.size() > 0) removeModules(modulesToUnload); if (modulesToLoad.size() > 0) addModules(modulesToLoad); if (modulesToUpdate.size() > 0) updateModules(modulesToUpdate); } public void pause() { myDispatcher.pause(); } public void proceed() { myDispatcher.proceed(); } private class MyModuleEventVisitor implements SModuleEventVisitor { private final Set<ReloadableModuleBase> myModulesToUpdate = new LinkedHashSet<ReloadableModuleBase>(); private final Set<ReloadableModuleBase> myModulesToLoad = new LinkedHashSet<ReloadableModuleBase>(); private final Set<SModuleReference> myModulesToUnload = new LinkedHashSet<SModuleReference>(); @Override public void visit(SModuleAddedEvent event) { SModule module = event.getModule(); if (module instanceof ReloadableModule) { assert module instanceof ReloadableModuleBase; myModulesToLoad.add((ReloadableModuleBase) module); } } @Override public void visit(SModuleRemovingEvent event) { // NOP } @Override public void visit(SModuleChangedEvent event) { SModule module = event.getModule(); if (module instanceof ReloadableModule) { assert module instanceof ReloadableModuleBase; myModulesToUpdate.add((ReloadableModuleBase) module); } } @Override public void visit(SModuleRemovedEvent event) { SModuleReference mRef = event.getModuleReference(); removeUnloaded(mRef); myModulesToUnload.add(mRef); } private void removeUnloaded(SModuleReference mRef) { for (Iterator<ReloadableModuleBase> iterator = myModulesToLoad.iterator(); iterator.hasNext();) { ReloadableModule module = iterator.next(); SModuleReference ref = module.getModuleReference(); if (mRef.equals(ref)) iterator.remove(); } for (Iterator<ReloadableModuleBase> iterator = myModulesToUpdate.iterator(); iterator.hasNext();) { ReloadableModule module = iterator.next(); SModuleReference ref = module.getModuleReference(); if (mRef.equals(ref)) iterator.remove(); } } public List<SModuleReference> getModulesToUnload() { return new ArrayList<SModuleReference>(myModulesToUnload); } public List<ReloadableModuleBase> getModulesToLoad() { return new ArrayList<ReloadableModuleBase>(myModulesToLoad); } public List<ReloadableModuleBase> getModulesToUpdate() { return new ArrayList<ReloadableModuleBase>(myModulesToUpdate); } } }