/******************************************************************************* * Copyright (c) 2012 Google, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Google, Inc. - initial API and implementation *******************************************************************************/ package com.windowtester.internal.runtime.condition; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import com.windowtester.internal.debug.IRuntimePluginTraceOptions; import com.windowtester.internal.debug.Logger; import com.windowtester.internal.debug.TraceHandler; import com.windowtester.runtime.IUIContext; import com.windowtester.runtime.condition.ICondition; import com.windowtester.runtime.condition.IConditionHandler; import com.windowtester.runtime.condition.IConditionMonitor; import com.windowtester.runtime.condition.IHandler; import com.windowtester.runtime.condition.IUICondition; import com.windowtester.runtime.internal.condition.IConditionWithIdle; /** * A condition monitor checks for registered conditions and activates their associated * handlers. See {@link com.windowtester.runtime.condition.IConditionMonitor} for more * information. */ public class ConditionMonitor implements IConditionMonitor { /** * Global condition monitor */ private static final ConditionMonitor ROOT = new ConditionMonitor(null); /** * Flag indicating whether conditions are currently being processed and to prevent * recursive checking of conditions. Synchronize against this {@link #_mappings} * before accessing this field. */ private boolean _isProcessing = false; /** * A list of {@link ICondition} / {@link IHandler} pairs maintained and processed by * the receiver. Synchronize against this field before accessing either this field or * {@link #_isProcessing}. */ private final List _mappings = new ArrayList(); /** * This field is used to cache a copy of the {@link #_mappings} field when processing * conditions and associated handlers, and set to <code>null</code> when the cache * is invalidated by accessor methods such as {@link #add(ICondition, IHandler)}. * Synchronize against {@link #_mappings} before accessing this field. */ private ConditionMapping[] _cachedMappings = null; /** * The parent condition monitor or <code>null</code> if none */ private final IConditionMonitor _parent; /** * Construct a new instance that wrappers the specified parent condition monitor. * NOTE: Callers should not access this method directly, but rather use either * {@link #getInstance()} to obtain the global condition monitor or obtain the * condition monitor local to a particular {@link IUIContext} via * {@link IUIContext#getAdapter(Class)} by passing {@link IConditionMonitor} as the * argument. * * @param parent the parent condition monitor or <code>null</code> if none */ public ConditionMonitor(IConditionMonitor parent) { _parent = parent; } /** * Answer the global condition monitor. Condition monitors local to a particular * {@link IUIContext} can be accessed via {@link IUIContext#getAdapter(Class)} * by passing {@link IConditionMonitor} as the argument. * * @return the global monitor (not <code>null</code>) */ public static ConditionMonitor getInstance() { return ROOT; } // ////////////////////////////////////////////////////////////////////////// // // Accessors // // ////////////////////////////////////////////////////////////////////////// /** * Add the specified condition and associated handler to the receiver so that it is * included the next time that conditions are processed. WARNING! No checking is * performed to prevent condition/handler pairs from being added multiple times. * * @param condition the condition to be tested (not <code>null</code>) * @param handler the handler to be activated if the condition is satisfied */ public void add(ICondition condition, IHandler handler) { if (condition == null || handler == null) throw new IllegalArgumentException("Arguments cannot be null"); synchronized (_mappings) { _mappings.add(new ConditionMapping(condition, handler)); _cachedMappings = null; } } /** * @see IConditionMonitor#add(com.windowtester.runtime.condition.IConditionHandler) */ public void add(IConditionHandler conditionhandler) { add(conditionhandler, conditionhandler); } /** * @see IConditionMonitor#removeAll() */ public void removeAll() { synchronized (_mappings) { _mappings.clear(); _cachedMappings = null; // invalidate cache } } /** * Remove this condition and it's associated handler from the monitor. * * @param condition the condition to remove (not <code>null</code>) */ public void removeHandler(ICondition condition) { if (condition == null) throw new IllegalArgumentException("Condition cannot be null"); synchronized (_mappings) { ConditionMapping mapping = findMapping(condition); if (mapping == null) return; _mappings.remove(mapping); _cachedMappings = null; // invalidate cache } } /** * Get a copy of this monitor's conditions. (Note this method is Non-API.) * * @return a copy of this monitors condition key-set */ public ICondition[] getConditions() { List conditions = new ArrayList(); ConditionMapping mapping; synchronized (_mappings) { for (Iterator iter = _mappings.iterator(); iter.hasNext();) { mapping = (ConditionMapping) iter.next(); conditions.add(mapping.condition); } } return (ICondition[]) conditions.toArray(new ICondition[]{}); } // ////////////////////////////////////////////////////////////////////////// // // Processing // // ////////////////////////////////////////////////////////////////////////// /** * Utility method for testing conditions. If the specified condition is an instance * of {@link IUICondition} then {@link IUICondition#testUI(IUIContext)} is called * rather than {@link ICondition#test()}. * * @param ui the UI context (not <code>null</code>) * @param condition the condition to be tested * @return <code>true</code> if the condition is true, else <code>false</code> */ public static boolean test(IUIContext ui, ICondition condition) { if (condition instanceof IUICondition) return ((IUICondition) condition).testUI(ui); if (condition instanceof IConditionWithIdle) return ((ICondition)condition).test(); return condition.test(); } /** * Process all condition/handler pairs by checking each condition and calling the * associated handlers for any conditions that are satisfied. Nested calls to this * method return immediately without taking any action. * * @param ui the UIContext instance for use in condition handling * @return one of the following flags indicating what was processed: * {@link #PROCESS_NONE} if conditions were processed but no conditions were * satisfied, {@link #PROCESS_ONE_OR_MORE} if conditions were processed and * at least on condition was satisfied, {@link #PROCESS_RECURSIVE} if * conditions were already being processed and no additional action was taken. */ public int process(IUIContext ui) { // Prevent recursion and make a copy of the current condition mappings ConditionMapping[] copyOfMappings; synchronized (_mappings) { if (_isProcessing) return PROCESS_RECURSIVE; _isProcessing = true; if (_cachedMappings == null) _cachedMappings = (ConditionMapping[]) _mappings.toArray(new ConditionMapping[_mappings.size()]); copyOfMappings = _cachedMappings; } // Iterate over the current mappings and ensure that _isProcessing is reset boolean match = false; try { for (int i = 0; i < copyOfMappings.length; i++) { if (test(ui, copyOfMappings[i].condition)) { TraceHandler.trace(IRuntimePluginTraceOptions.CONDITIONS, "calling handle: " + copyOfMappings[i].handler); try { copyOfMappings[i].handler.handle(ui); match = true; } catch (Exception e) { Logger.log("Unexpected exception when processing conditions: " + e, e); } } } if (_parent != null && _parent.process(ui) == PROCESS_ONE_OR_MORE) match = true; } finally { synchronized (_mappings) { _isProcessing = false; } } return match ? PROCESS_ONE_OR_MORE : PROCESS_NONE; } // ////////////////////////////////////////////////////////////////////////// // // Utilities // // ////////////////////////////////////////////////////////////////////////// /** * Internal class associating a {@link com.windowtester.runtime.condition.ICondition} with * a {@link com.windowtester.runtime.condition.IHandler} */ private static class ConditionMapping { final ICondition condition; final IHandler handler; public ConditionMapping(ICondition c, IHandler h) { condition = c; handler = h; } } /** * Find the mapping associated with this condition (or <code>null</code> if no * associated mapping can be found. */ private ConditionMapping findMapping(ICondition condition) { ConditionMapping mapping; synchronized (_mappings) { for (Iterator iter = _mappings.iterator(); iter.hasNext();) { mapping = (ConditionMapping) iter.next(); if (mapping.condition == condition) return mapping; } } return null; } }