diff --git a/src/main/java/de/extio/game_engine/module/AbstractClientModule.java b/src/main/java/de/extio/game_engine/module/AbstractClientModule.java new file mode 100644 index 0000000..0895968 --- /dev/null +++ b/src/main/java/de/extio/game_engine/module/AbstractClientModule.java @@ -0,0 +1,42 @@ +package de.extio.game_engine.module; + +/** + * Root of all UI client modules + */ +public abstract class AbstractClientModule extends AbstractModule { + + final static int MODULE_PRIORITY_LOW = 25; + + final static int MODULE_PRIORITY_NORMAL = 50; + + final static int MODULE_PRIORITY_HIGH = 75; + + public AbstractClientModule() { + + } + + public int getPriority() { + return MODULE_PRIORITY_NORMAL; + } + + public boolean isAlwaysDisplay() { + return false; + } + + public void onShow() { + + } + + public void onHide() { + + } + + public void runUiPreEvents() { + + } + + public void runUiPostEvents() { + + } + +} diff --git a/src/main/java/de/extio/game_engine/module/AbstractModule.java b/src/main/java/de/extio/game_engine/module/AbstractModule.java new file mode 100644 index 0000000..0964a09 --- /dev/null +++ b/src/main/java/de/extio/game_engine/module/AbstractModule.java @@ -0,0 +1,43 @@ +package de.extio.game_engine.module; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Root of all modules + */ +public abstract class AbstractModule { + + protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractModule.class); + + public AbstractModule() { + + } + + public void onLoad() { + + } + + public void onUnload() { + + } + + public void onActivate() { + + } + + public void onDeactivate() { + + } + + + public void run() { + + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/src/main/java/de/extio/game_engine/module/ModuleManager.java b/src/main/java/de/extio/game_engine/module/ModuleManager.java new file mode 100644 index 0000000..73ce36d --- /dev/null +++ b/src/main/java/de/extio/game_engine/module/ModuleManager.java @@ -0,0 +1,41 @@ +package de.extio.game_engine.module; + +import java.util.List; + +public interface ModuleManager { + + void loadModule(Class clazz); + + void unloadModule(Class clazz); + + List getModulesAll(); + + List getModulesActive(); + + List getModulesActiveClientModules(); + + List getModulesDisplayedClientModules(); + + void changeActiveState(String className, boolean active); + + void changeActiveState(Class clazz, boolean active); + + void deactivateAll(); + + void changeDisplayState(String className, boolean active); + + void changeDisplayState(Class clazz, boolean display); + + boolean isDisplayed(String className); + + boolean isDisplayed(Class clazz); + + void hideAll(); + + void hideExcept(String... classNames); + + void restoreVisibility(); + + boolean isModal(); + +} diff --git a/src/main/java/de/extio/game_engine/module/ModuleManagerAutoConfiguration.java b/src/main/java/de/extio/game_engine/module/ModuleManagerAutoConfiguration.java new file mode 100644 index 0000000..b89599e --- /dev/null +++ b/src/main/java/de/extio/game_engine/module/ModuleManagerAutoConfiguration.java @@ -0,0 +1,18 @@ +package de.extio.game_engine.module; + +import java.util.List; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; + +@AutoConfiguration +@ConditionalOnProperty(name = "game-engine.module.enabled", havingValue = "true", matchIfMissing = true) +public class ModuleManagerAutoConfiguration { + + @Bean + ModuleManager moduleManager(final List initialModules) { + return new ModuleManagerImpl(initialModules); + } + +} diff --git a/src/main/java/de/extio/game_engine/module/ModuleManagerImpl.java b/src/main/java/de/extio/game_engine/module/ModuleManagerImpl.java new file mode 100644 index 0000000..5ea9973 --- /dev/null +++ b/src/main/java/de/extio/game_engine/module/ModuleManagerImpl.java @@ -0,0 +1,297 @@ +package de.extio.game_engine.module; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.InitializingBean; + +public class ModuleManagerImpl implements InitializingBean, ModuleManager { + + private static final Logger LOGGER = LogManager.getLogger(); + + private final List modulesInitial; + + private final List modulesAll = new ArrayList<>(); + + private List modulesAllView = List.of(); + + private List modulesActiveView = List.of(); + + private List modulesActiveClientModulesView = List.of(); + + private List modulesDisplayedClientModulesView = List.of(); + + private final List modulesActive = new ArrayList<>(); + + private final List modulesDisplayed = new ArrayList<>(); + + private final Deque> lastVisibleStates = new ArrayDeque<>(); + + public ModuleManagerImpl(final List initialModules) { + this.modulesInitial = initialModules; + } + + @Override + public void afterPropertiesSet() throws Exception { + if (this.modulesInitial == null || this.modulesInitial.isEmpty()) { + return; + } + this.modulesInitial.forEach(module -> this.invokeSafe(module, AbstractModule::onLoad)); + this.modulesAll.addAll(this.modulesInitial); + this.sortModules(this.modulesAll); + this.modulesAllView = List.copyOf(this.modulesAll); + this.modulesInitial.clear(); + } + + @Override + public List getModulesAll() { + return this.modulesAllView; + } + + @Override + public List getModulesActive() { + return this.modulesActiveView; + } + + @Override + public List getModulesActiveClientModules() { + return this.modulesActiveClientModulesView; + } + + @Override + public List getModulesDisplayedClientModules() { + return modulesDisplayedClientModulesView; + } + + @Override + public void loadModule(final Class clazz) { + if (this.modulesAll.stream().anyMatch(module -> clazz.isInstance(module))) { + LOGGER.debug("AbstractModule already loaded: " + clazz.getName()); + return; + } + + LOGGER.debug("Loading module " + clazz.getName()); + + try { + final AbstractModule module = AbstractModule.class.cast(clazz.getDeclaredConstructor().newInstance()); + this.invokeSafe(module, m -> m.onLoad()); + this.modulesAll.add(module); + this.sortModules(this.modulesAll); + this.modulesAllView = List.copyOf(this.modulesAll); + } + catch (final Exception e) { + LOGGER.error("Error loading module " + clazz.getName(), e); + } + } + + @Override + public void unloadModule(final Class clazz) { + try { + final AbstractModule module = this.modulesAll.stream().filter(mod -> clazz.isInstance(mod)).findAny().orElse(null); + + if (module == null) { + LOGGER.debug("AbstractModule not loaded: " + clazz.getName()); + return; + } + + this.changeActiveState(clazz, false); + this.modulesAll.remove(module); + this.sortModules(this.modulesAll); + this.modulesAllView = List.copyOf(this.modulesAll); + + this.invokeSafe(module, m -> m.onUnload()); + + LOGGER.debug("Unloaded module " + clazz.getName()); + } + catch (final Exception e) { + LOGGER.error("Error unloading module " + clazz.getName(), e); + } + } + + @Override + public void changeActiveState(final String className, final boolean active) { + try { + final var clazz = Class.forName(className); + this.changeActiveState(clazz.asSubclass(AbstractModule.class), active); + } + catch (final ClassNotFoundException e) { + LOGGER.error("Class not found: " + className, e); + } + } + + @Override + public void changeActiveState(final Class clazz, final boolean active) { + this.modulesActiveView.stream() + .filter(module -> clazz.isAssignableFrom(module.getClass())) + .findAny() + .ifPresentOrElse(module -> { + if (!active) { + if (module instanceof final AbstractClientModule clientModule) { + this.changeDisplayState(clientModule.getClass(), false); + } + this.invokeSafe(module, AbstractModule::onDeactivate); + this.modulesActive.remove(module); + this.sortModules(this.modulesActive); + this.modulesActiveView = List.copyOf(this.modulesActive); + this.modulesActiveClientModulesView = this.modulesActive.stream() + .filter(m -> m instanceof AbstractClientModule) + .map(m -> (AbstractClientModule) m) + .toList(); + this.modulesDisplayedClientModulesView = List.copyOf(this.modulesDisplayed); + + LOGGER.debug("Deactivated " + clazz.getName()); + } + }, () -> { + if (active) { + this.modulesAll.stream() + .filter(module -> clazz.isAssignableFrom(module.getClass())) + .findFirst() + .ifPresent(module -> { + this.invokeSafe(module, AbstractModule::onActivate); + if (module instanceof final AbstractClientModule clientModule && clientModule.isAlwaysDisplay()) { + this.changeDisplayState(clientModule.getClass(), true); + } + this.modulesActive.add(module); + this.sortModules(this.modulesActive); + this.modulesActiveView = List.copyOf(this.modulesActive); + this.modulesActiveClientModulesView = this.modulesActive.stream() + .filter(m -> m instanceof AbstractClientModule) + .map(m -> (AbstractClientModule) m) + .toList(); + this.modulesDisplayedClientModulesView = List.copyOf(this.modulesDisplayed); + + LOGGER.debug("Activated " + clazz.getName()); + }); + } + }); + } + + @Override + public void deactivateAll() { + for (final AbstractModule module : this.modulesActiveView) { + this.changeActiveState(module.getClass(), false); + } + this.lastVisibleStates.clear(); + } + + @Override + public void changeDisplayState(final String className, final boolean active) { + try { + final var clazz = Class.forName(className); + this.changeDisplayState(clazz.asSubclass(AbstractClientModule.class), active); + } + catch (final ClassNotFoundException e) { + LOGGER.error("Class not found: " + className, e); + } + } + + @Override + public void changeDisplayState(final Class clazz, final boolean display) { + for (final var module : this.modulesAll) { + if (module instanceof final AbstractClientModule clientModule && clazz.isAssignableFrom(module.getClass())) { + final boolean curDisplayed = this.modulesDisplayed.stream().anyMatch(mod -> clazz.isAssignableFrom(mod.getClass())); + + final boolean doDisplay = display || clientModule.isAlwaysDisplay(); + if (doDisplay && !curDisplayed) { + this.modulesDisplayed.add(clientModule); + this.invokeSafe(clientModule, m -> ((AbstractClientModule) m).onShow()); + } + else if (!doDisplay && curDisplayed) { + this.modulesDisplayed.removeIf(mod -> clazz.isAssignableFrom(mod.getClass())); + this.invokeSafe(clientModule, m -> ((AbstractClientModule) m).onHide()); + } + this.modulesDisplayedClientModulesView = List.copyOf(this.modulesDisplayed); + + break; + } + } + } + + @Override + public boolean isDisplayed(final String className) { + try { + final var clazz = Class.forName(className); + return this.isDisplayed(clazz.asSubclass(AbstractClientModule.class)); + } + catch (final ClassNotFoundException e) { + LOGGER.error("Class not found: " + className, e); + return false; + } + } + + @Override + public boolean isDisplayed(final Class clazz) { + return this.modulesDisplayed.stream().anyMatch(m -> clazz.isAssignableFrom(m.getClass())); + } + + @Override + public void hideAll() { + this.lastVisibleStates.clear(); + + for (final var module : List.copyOf(this.modulesDisplayed)) { + if ((module instanceof final AbstractClientModule clientModule) && !clientModule.isAlwaysDisplay()) { + this.changeDisplayState(clientModule.getClass(), false); + } + } + } + + @Override + @SafeVarargs + public final void hideExcept(final String... classNames) { + final List state = new ArrayList<>(); + final var keep = new java.util.HashSet<>(java.util.Arrays.asList(classNames)); + + for (final var module : List.copyOf(this.modulesDisplayed)) { + if (module instanceof final AbstractClientModule clientModule && + !clientModule.isAlwaysDisplay() && + !keep.contains(clientModule.getClass().getName())) { + this.changeDisplayState(clientModule.getClass(), false); + state.add(clientModule); + } + } + + if (!state.isEmpty()) { + this.lastVisibleStates.push(state); + } + } + + @Override + public void restoreVisibility() { + if (this.lastVisibleStates.isEmpty()) { + return; + } + + for (final AbstractClientModule module : this.lastVisibleStates.pop()) { + this.changeDisplayState(module.getClass(), true); + } + } + + @Override + public boolean isModal() { + return !this.lastVisibleStates.isEmpty(); + } + + private void invokeSafe(final AbstractModule module, final Consumer consumer) { + try { + consumer.accept(module); + } + catch (final Exception exc) { + LOGGER.error("Error in module " + module.getClass().getName(), exc); + } + } + + private void sortModules(final List modules) { + modules.sort((o1, o2) -> { + if (o1 instanceof final AbstractClientModule a1 && o2 instanceof final AbstractClientModule a2) { + return Integer.compare(a2.getPriority(), a1.getPriority()); + } + return Boolean.compare(o1 instanceof AbstractClientModule, o2 instanceof AbstractClientModule); + }); + } + +} diff --git a/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index a7aea4b..24817c5 100644 --- a/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -3,3 +3,4 @@ de.extio.game_engine.keyboard.KeycodeAutoConfiguration de.extio.game_engine.renderer.RendererAutoConfiguration de.extio.game_engine.renderer.g2d.G2DAutoConfiguration de.extio.game_engine.storage.StorageServiceAutoConfiguration +de.extio.game_engine.module.ModuleManagerAutoConfiguration diff --git a/src/test/java/de/extio/game_engine/module/ModuleManagerTest.java b/src/test/java/de/extio/game_engine/module/ModuleManagerTest.java new file mode 100644 index 0000000..a853b12 --- /dev/null +++ b/src/test/java/de/extio/game_engine/module/ModuleManagerTest.java @@ -0,0 +1,504 @@ +package de.extio.game_engine.module; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class ModuleManagerTest { + + private ModuleManagerImpl moduleManager; + + private TestModule testModule; + + private TestClientModule testClientModule; + + @BeforeEach + public void setUp() throws Exception { + this.testModule = new TestModule(); + this.testClientModule = new TestClientModule(); + + final List initialModules = new ArrayList<>(); + initialModules.add(this.testModule); + initialModules.add(this.testClientModule); + + this.moduleManager = new ModuleManagerImpl(initialModules); + this.moduleManager.afterPropertiesSet(); + } + + @Test + public void testAfterPropertiesSet() { + assertEquals(2, this.moduleManager.getModulesAll().size()); + assertTrue(this.testModule.onLoadCalled); + assertTrue(this.testClientModule.onLoadCalled); + } + + @Test + public void testAfterPropertiesSet_EmptyModules() throws Exception { + final ModuleManagerImpl emptyManager = new ModuleManagerImpl(List.of()); + emptyManager.afterPropertiesSet(); + + assertTrue(emptyManager.getModulesAll().isEmpty()); + } + + @Test + public void testAfterPropertiesSet_NullModules() throws Exception { + final ModuleManagerImpl nullManager = new ModuleManagerImpl(null); + nullManager.afterPropertiesSet(); + + assertTrue(nullManager.getModulesAll().isEmpty()); + } + + @Test + public void testGetModulesAll() { + final List modules = this.moduleManager.getModulesAll(); + + assertEquals(2, modules.size()); + assertTrue(modules.contains(this.testModule)); + assertTrue(modules.contains(this.testClientModule)); + } + + @Test + public void testGetModulesActive_InitiallyEmpty() { + assertTrue(this.moduleManager.getModulesActive().isEmpty()); + } + + @Test + public void testLoadModule() { + this.moduleManager.loadModule(AlwaysDisplayModule.class); + + assertEquals(3, this.moduleManager.getModulesAll().size()); + assertTrue(this.moduleManager.getModulesAll().stream().anyMatch(m -> m instanceof AlwaysDisplayModule)); + } + + @Test + public void testLoadModule_AlreadyLoaded() { + final int initialSize = this.moduleManager.getModulesAll().size(); + + this.moduleManager.loadModule(TestModule.class); + + assertEquals(initialSize, this.moduleManager.getModulesAll().size()); + } + + @Test + public void testUnloadModule() { + this.moduleManager.unloadModule(TestModule.class); + + assertEquals(1, this.moduleManager.getModulesAll().size()); + assertFalse(this.moduleManager.getModulesAll().contains(this.testModule)); + assertTrue(this.testModule.onUnloadCalled); + } + + @Test + public void testUnloadModule_NotLoaded() { + final int initialSize = this.moduleManager.getModulesAll().size(); + + this.moduleManager.unloadModule(AlwaysDisplayModule.class); + + assertEquals(initialSize, this.moduleManager.getModulesAll().size()); + } + + @Test + public void testUnloadModule_DeactivatesFirst() { + this.moduleManager.changeActiveState(TestModule.class, true); + assertTrue(this.moduleManager.getModulesActive().contains(this.testModule)); + + this.moduleManager.unloadModule(TestModule.class); + + assertFalse(this.moduleManager.getModulesActive().contains(this.testModule)); + assertTrue(this.testModule.onDeactivateCalled); + } + + @Test + public void testChangeActiveState_Activate() { + this.moduleManager.changeActiveState(TestModule.class, true); + + assertTrue(this.moduleManager.getModulesActive().contains(this.testModule)); + assertTrue(this.testModule.onActivateCalled); + } + + @Test + public void testChangeActiveState_Deactivate() { + this.moduleManager.changeActiveState(TestModule.class, true); + this.testModule.onActivateCalled = false; + + this.moduleManager.changeActiveState(TestModule.class, false); + + assertFalse(this.moduleManager.getModulesActive().contains(this.testModule)); + assertTrue(this.testModule.onDeactivateCalled); + } + + @Test + public void testChangeActiveState_AlreadyActive() { + this.moduleManager.changeActiveState(TestModule.class, true); + this.testModule.onActivateCalled = false; + + this.moduleManager.changeActiveState(TestModule.class, true); + + assertFalse(this.testModule.onActivateCalled); + } + + @Test + public void testChangeActiveState_AlreadyInactive() { + this.moduleManager.changeActiveState(TestModule.class, false); + + assertFalse(this.testModule.onDeactivateCalled); + } + + @Test + public void testChangeActiveState_ByClassName() { + this.moduleManager.changeActiveState(TestModule.class.getName(), true); + + assertTrue(this.moduleManager.getModulesActive().contains(this.testModule)); + assertTrue(this.testModule.onActivateCalled); + } + + @Test + public void testChangeActiveState_AlwaysDisplayModule() { + this.moduleManager.loadModule(AlwaysDisplayModule.class); + + this.moduleManager.changeActiveState(AlwaysDisplayModule.class, true); + + assertTrue(this.moduleManager.isDisplayed(AlwaysDisplayModule.class)); + } + + @Test + public void testDeactivateAll() { + this.moduleManager.changeActiveState(TestModule.class, true); + this.moduleManager.changeActiveState(TestClientModule.class, true); + + this.moduleManager.deactivateAll(); + + assertTrue(this.moduleManager.getModulesActive().isEmpty()); + assertTrue(this.testModule.onDeactivateCalled); + assertTrue(this.testClientModule.onDeactivateCalled); + } + + @Test + public void testChangeDisplayState_Show() { + this.moduleManager.changeActiveState(TestClientModule.class, true); + this.testClientModule.onShowCalled = false; + + this.moduleManager.changeDisplayState(TestClientModule.class, true); + + assertTrue(this.moduleManager.isDisplayed(TestClientModule.class)); + assertTrue(this.testClientModule.onShowCalled); + } + + @Test + public void testChangeDisplayState_Hide() { + this.moduleManager.changeActiveState(TestClientModule.class, true); + this.moduleManager.changeDisplayState(TestClientModule.class, true); + this.testClientModule.onHideCalled = false; + + this.moduleManager.changeDisplayState(TestClientModule.class, false); + + assertFalse(this.moduleManager.isDisplayed(TestClientModule.class)); + assertTrue(this.testClientModule.onHideCalled); + } + + @Test + public void testChangeDisplayState_AlreadyShown() { + this.moduleManager.changeActiveState(TestClientModule.class, true); + this.moduleManager.changeDisplayState(TestClientModule.class, true); + this.testClientModule.onShowCalled = false; + + this.moduleManager.changeDisplayState(TestClientModule.class, true); + + assertFalse(this.testClientModule.onShowCalled, "onShow should not be called when already shown"); + } + + @Test + public void testChangeDisplayState_AlreadyHidden() { + this.testClientModule.onHideCalled = false; + + this.moduleManager.changeDisplayState(TestClientModule.class, false); + + assertFalse(this.testClientModule.onHideCalled, "onHide should not be called when already hidden"); + } + + @Test + public void testChangeDisplayState_ByClassName() { + this.moduleManager.changeActiveState(TestClientModule.class, true); + this.testClientModule.onShowCalled = false; + + this.moduleManager.changeDisplayState(TestClientModule.class.getName(), true); + + assertTrue(this.moduleManager.isDisplayed(TestClientModule.class)); + } + + @Test + public void testChangeDisplayState_AlwaysDisplayModule() { + this.moduleManager.loadModule(AlwaysDisplayModule.class); + this.moduleManager.changeActiveState(AlwaysDisplayModule.class, true); + + this.moduleManager.changeDisplayState(AlwaysDisplayModule.class, false); + + assertTrue(this.moduleManager.isDisplayed(AlwaysDisplayModule.class)); + } + + @Test + public void testIsDisplayed_ByClassName() { + this.moduleManager.changeActiveState(TestClientModule.class, true); + this.moduleManager.changeDisplayState(TestClientModule.class, true); + + assertTrue(this.moduleManager.isDisplayed(TestClientModule.class.getName())); + } + + @Test + public void testHideAll() { + this.moduleManager.changeActiveState(TestClientModule.class, true); + this.moduleManager.changeDisplayState(TestClientModule.class, true); + this.testClientModule.onHideCalled = false; + + this.moduleManager.hideAll(); + + assertFalse(this.moduleManager.isDisplayed(TestClientModule.class)); + assertTrue(this.testClientModule.onHideCalled); + } + + @Test + public void testHideAll_DoesNotHideAlwaysDisplay() { + this.moduleManager.loadModule(AlwaysDisplayModule.class); + this.moduleManager.changeActiveState(AlwaysDisplayModule.class, true); + this.moduleManager.changeActiveState(TestClientModule.class, true); + this.moduleManager.changeDisplayState(TestClientModule.class, true); + + this.moduleManager.hideAll(); + + assertTrue(this.moduleManager.isDisplayed(AlwaysDisplayModule.class)); + assertFalse(this.moduleManager.isDisplayed(TestClientModule.class)); + } + + @Test + public void testHideExcept() { + this.moduleManager.loadModule(AlwaysDisplayModule.class); + this.moduleManager.changeActiveState(TestClientModule.class, true); + this.moduleManager.changeDisplayState(TestClientModule.class, true); + this.moduleManager.changeActiveState(AlwaysDisplayModule.class, true); + this.testClientModule.onHideCalled = false; + + this.moduleManager.hideExcept(AlwaysDisplayModule.class.getName()); + + assertTrue(this.moduleManager.isDisplayed(AlwaysDisplayModule.class)); + assertFalse(this.moduleManager.isDisplayed(TestClientModule.class)); + assertTrue(this.testClientModule.onHideCalled); + } + + @Test + public void testHideExcept_MultipleExceptions() { + this.moduleManager.loadModule(AlwaysDisplayModule.class); + this.moduleManager.loadModule(HighPriorityModule.class); + this.moduleManager.changeActiveState(TestClientModule.class, true); + this.moduleManager.changeDisplayState(TestClientModule.class, true); + this.moduleManager.changeActiveState(AlwaysDisplayModule.class, true); + this.moduleManager.changeActiveState(HighPriorityModule.class, true); + this.moduleManager.changeDisplayState(HighPriorityModule.class, true); + + this.moduleManager.hideExcept( + AlwaysDisplayModule.class.getName(), + HighPriorityModule.class.getName() + ); + + assertTrue(this.moduleManager.isDisplayed(AlwaysDisplayModule.class)); + assertTrue(this.moduleManager.isDisplayed(HighPriorityModule.class)); + assertFalse(this.moduleManager.isDisplayed(TestClientModule.class)); + } + + @Test + public void testRestoreVisibility() { + this.moduleManager.changeActiveState(TestClientModule.class, true); + this.moduleManager.changeDisplayState(TestClientModule.class, true); + + this.moduleManager.hideExcept(); + assertFalse(this.moduleManager.isDisplayed(TestClientModule.class)); + this.testClientModule.onShowCalled = false; + + this.moduleManager.restoreVisibility(); + + assertTrue(this.moduleManager.isDisplayed(TestClientModule.class)); + assertTrue(this.testClientModule.onShowCalled); + } + + @Test + public void testRestoreVisibility_NoState() { + this.moduleManager.restoreVisibility(); + + assertFalse(this.moduleManager.isDisplayed(TestClientModule.class)); + } + + @Test + public void testIsModal() { + assertFalse(this.moduleManager.isModal()); + + this.moduleManager.changeActiveState(TestClientModule.class, true); + this.moduleManager.changeDisplayState(TestClientModule.class, true); + this.moduleManager.hideExcept(); + + assertTrue(this.moduleManager.isModal()); + + this.moduleManager.restoreVisibility(); + + assertFalse(this.moduleManager.isModal()); + } + + @Test + public void testModuleSorting_Priority() { + this.moduleManager.loadModule(HighPriorityModule.class); + + final List modules = this.moduleManager.getModulesAll(); + + assertEquals(3, modules.size()); + + int highPriorityIndex = -1; + int normalPriorityIndex = -1; + int nonClientIndex = -1; + + for (int i = 0; i < modules.size(); i++) { + if (modules.get(i) instanceof HighPriorityModule) { + highPriorityIndex = i; + } + else if (modules.get(i) instanceof TestClientModule) { + normalPriorityIndex = i; + } + else if (modules.get(i) instanceof TestModule) { + nonClientIndex = i; + } + } + + assertTrue(highPriorityIndex >= 0); + assertTrue(normalPriorityIndex >= 0); + assertTrue(nonClientIndex >= 0); + assertTrue(highPriorityIndex < normalPriorityIndex, + "High priority (" + highPriorityIndex + ") should come before normal (" + normalPriorityIndex + ")"); + assertTrue(nonClientIndex < normalPriorityIndex, + "Non-client (" + nonClientIndex + ") should come before client (" + normalPriorityIndex + ")"); + assertTrue(nonClientIndex < highPriorityIndex, + "Non-client (" + nonClientIndex + ") should come before high priority client (" + highPriorityIndex + ")"); + } + + @Test + public void testDeactivate_HidesDisplayedClientModule() { + this.moduleManager.changeActiveState(TestClientModule.class, true); + this.moduleManager.changeDisplayState(TestClientModule.class, true); + this.testClientModule.onHideCalled = false; + + this.moduleManager.changeActiveState(TestClientModule.class, false); + + assertFalse(this.moduleManager.isDisplayed(TestClientModule.class)); + assertTrue(this.testClientModule.onHideCalled); + } + + @Test + public void testDeactivateAll_ClearsVisibilityStack() { + this.moduleManager.changeActiveState(TestClientModule.class, true); + this.moduleManager.changeDisplayState(TestClientModule.class, true); + this.moduleManager.hideExcept(); + + assertTrue(this.moduleManager.isModal()); + + this.moduleManager.deactivateAll(); + + assertFalse(this.moduleManager.isModal()); + } + + static class TestModule extends AbstractModule { + + boolean onLoadCalled = false; + + boolean onUnloadCalled = false; + + boolean onActivateCalled = false; + + boolean onDeactivateCalled = false; + + @Override + public void onLoad() { + this.onLoadCalled = true; + } + + @Override + public void onUnload() { + this.onUnloadCalled = true; + } + + @Override + public void onActivate() { + this.onActivateCalled = true; + } + + @Override + public void onDeactivate() { + this.onDeactivateCalled = true; + } + + } + + static class TestClientModule extends AbstractClientModule { + + boolean onLoadCalled = false; + + boolean onUnloadCalled = false; + + boolean onActivateCalled = false; + + boolean onDeactivateCalled = false; + + boolean onShowCalled = false; + + boolean onHideCalled = false; + + @Override + public void onLoad() { + this.onLoadCalled = true; + } + + @Override + public void onUnload() { + this.onUnloadCalled = true; + } + + @Override + public void onActivate() { + this.onActivateCalled = true; + } + + @Override + public void onDeactivate() { + this.onDeactivateCalled = true; + } + + @Override + public void onShow() { + this.onShowCalled = true; + } + + @Override + public void onHide() { + this.onHideCalled = true; + } + + } + + static class AlwaysDisplayModule extends AbstractClientModule { + + @Override + public boolean isAlwaysDisplay() { + return true; + } + + } + + static class HighPriorityModule extends AbstractClientModule { + + @Override + public int getPriority() { + return MODULE_PRIORITY_HIGH; + } + + } + +}