package org.fluentlenium.core.hook; import org.fluentlenium.core.FluentControl; import org.fluentlenium.core.components.ComponentInstantiator; import org.fluentlenium.core.proxy.LocatorProxies; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Stack; import java.util.function.Function; import java.util.function.Supplier; /** * Control implementation for hooks. * * @param <T> self type */ public class HookControlImpl<T> implements HookControl<T> { private final List<HookDefinition<?>> hookDefinitions = new ArrayList<>(); private Stack<List<HookDefinition<?>>> hookRestoreStack = new Stack<>(); private final HookChainBuilder hookChainBuilder; private final T self; private final Object proxy; private final Supplier<T> noHookInstanceSupplier; /** * Creates a new control implementation for hooks. * * @param self reference to object returned by chainnable calls * @param proxy proxy object to apply hooks on * @param control control interface * @param instantiator components instantiator * @param noHookInstanceSupplier supplier of new instance without any hook. */ public HookControlImpl(T self, Object proxy, FluentControl control, ComponentInstantiator instantiator, Supplier<T> noHookInstanceSupplier) { this.self = self; this.proxy = proxy; this.noHookInstanceSupplier = noHookInstanceSupplier; hookChainBuilder = new DefaultHookChainBuilder(control, instantiator); } /** * Get hook definitions. * * @return hook definitions */ public List<HookDefinition<?>> getHookDefinitions() { return hookDefinitions; } /** * Get hook restore stack. * * @return hook restore stack */ public Stack<List<HookDefinition<?>>> getHookRestoreStack() { return hookRestoreStack; } /** * Set the hook restore stack. * * @param hookRestoreStack hook restore stack */ public void setHookRestoreStack(Stack<List<HookDefinition<?>>> hookRestoreStack) { this.hookRestoreStack = hookRestoreStack; } /** * Removes hooks from definitions. * * @param definitions hook definitions * @param hooksToRemove hooks to remove */ public static void removeHooksFromDefinitions(Collection<HookDefinition<?>> definitions, Class<? extends FluentHook>... hooksToRemove) { Iterator<HookDefinition<?>> hookDefinitionsIterator = definitions.iterator(); List<Class<? extends FluentHook>> toRemoveHooks = Arrays.asList(hooksToRemove); while (hookDefinitionsIterator.hasNext()) { HookDefinition<?> next = hookDefinitionsIterator.next(); if (toRemoveHooks.contains(next.getHookClass())) { hookDefinitionsIterator.remove(); } } } private void backupHookDefinitions() { hookRestoreStack.add(new ArrayList<>(hookDefinitions)); } private void restoreHookDefinitions() { if (!hookRestoreStack.isEmpty()) { List<HookDefinition<?>> pop = hookRestoreStack.pop(); hookDefinitions.clear(); hookDefinitions.addAll(pop); } } @Override public T restoreHooks() { restoreHookDefinitions(); applyHooks(proxy, hookChainBuilder, hookDefinitions); return self; } @Override public <O, H extends FluentHook<O>> T withHook(Class<H> hook) { hookDefinitions.add(new HookDefinition<>(hook)); backupHookDefinitions(); applyHooks(proxy, hookChainBuilder, hookDefinitions); return self; } @Override public <O, H extends FluentHook<O>> T withHook(Class<H> hook, O options) { hookDefinitions.add(new HookDefinition<>(hook, options)); backupHookDefinitions(); applyHooks(proxy, hookChainBuilder, hookDefinitions); return self; } @Override public T noHook() { backupHookDefinitions(); hookDefinitions.clear(); applyHooks(proxy, hookChainBuilder, hookDefinitions); return self; } @Override public T noHook(Class<? extends FluentHook>... hooks) { backupHookDefinitions(); removeHooksFromDefinitions(hookDefinitions, hooks); applyHooks(proxy, hookChainBuilder, hookDefinitions); return self; } /** * Apply defined hooks on the proxy. * * @param proxy proxy * @param hookChainBuilder hook chain builder * @param hookDefinitions hook definitions */ protected void applyHooks(Object proxy, HookChainBuilder hookChainBuilder, List<HookDefinition<?>> hookDefinitions) { LocatorProxies.setHooks(proxy, hookChainBuilder, hookDefinitions); } @Override public <R> R noHook(Function<T, R> function) { noHook(); R functionReturn = function.apply(self); restoreHooks(); return functionReturn; } @Override public <R> R noHook(Class<? extends FluentHook> hook, Function<T, R> function) { noHook(hook); R functionReturn = function.apply(self); restoreHooks(); return functionReturn; } @Override public T noHookInstance() { return ((HookControl<T>) noHookInstanceSupplier.get()).noHook(); } @Override public T noHookInstance(Class<? extends FluentHook>... hooks) { HookControl<T> hookControl = (HookControl<T>) noHookInstanceSupplier.get(); for (HookDefinition definition : hookDefinitions) { hookControl.withHook(definition.getHookClass(), definition.getOptions()); } return hookControl.noHook(hooks); } }