blob: b392c6fafa2ea38dd58c527e8970c2a155b3ded4 [file] [log] [blame]
// Copyright 2004, 2005 The Apache Software Foundation
//
// 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 org.apache.tapestry.internal.event.impl;
import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
import org.apache.hivemind.util.Defense;
import org.apache.tapestry.IActionListener;
import org.apache.tapestry.IComponent;
import org.apache.tapestry.IForm;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.event.BrowserEvent;
import org.apache.tapestry.event.ResetEventListener;
import org.apache.tapestry.form.FormSupport;
import org.apache.tapestry.internal.event.ComponentEventProperty;
import org.apache.tapestry.internal.event.EventBoundListener;
import org.apache.tapestry.internal.event.IComponentEventInvoker;
import org.apache.tapestry.listener.ListenerInvoker;
import org.apache.tapestry.spec.IComponentSpecification;
import org.apache.tapestry.spec.IEventListener;
import java.util.*;
/**
* Implementation of {@link IComponentEventInvoker}.
*/
public class ComponentEventInvoker implements IComponentEventInvoker, ResetEventListener
{
static final ComponentEventProperty[] EMPTY_PROPERTIES = new ComponentEventProperty[0];
// Mapped component id path -> List of IEventListeners
private Map _components = new ConcurrentHashMap();
// Mapped form id path -> List of IEventListeners
private Map _formComponents = new ConcurrentHashMap();
// Used to invoke actual listener methods
private ListenerInvoker _invoker;
// Cached set of ComponentEventProperty[] arrays mapped to specific components
private Map _propertyCache = new ConcurrentHashMap();
/**
* {@inheritDoc}
*/
public void invokeListeners(IComponent component, IRequestCycle cycle, BrowserEvent event)
{
Defense.notNull(component, "component");
Defense.notNull(cycle, "cycle");
Defense.notNull(event, "event");
invokeComponentListeners(component, cycle, event);
invokeElementListeners(component, cycle, event);
}
/**
* {@inheritDoc}
*/
public void invokeFormListeners(FormSupport formSupport, final IRequestCycle cycle, final BrowserEvent event)
{
Defense.notNull(formSupport, "formSupport");
Defense.notNull(cycle, "cycle");
Defense.notNull(event, "event");
IForm form = formSupport.getForm();
String formIdPath = form.getExtendedId();
String targetId = (String)event.getTarget().get("id");
String componentId = event.getComponentId();
if (targetId == null || componentId == null)
return;
List comps = getFormEventListeners(formIdPath);
if (comps == null)
return;
boolean disableFocus = false;
for (int i=0; i < comps.size(); i++)
{
IComponentSpecification spec = (IComponentSpecification)comps.get(i);
EventBoundListener[] listeners = spec.getFormEvents(formIdPath, event);
IComponent target = null;
if (spec.isPageSpecification())
{
target = form.getPage();
} else {
target = findComponent(form.getPage().getComponents().values(), spec);
}
for (int e=0; e < listeners.length; e++)
{
// ensure ~only~ the method that targeted this event gets called!
if (!listeners[e].getComponentId().endsWith(componentId))
continue;
// clear validation errors but not input if async validation is
// disabled
if (!listeners[e].isValidateForm())
{
form.getDelegate().clearErrors();
}
// handle disabling focus
if (!disableFocus && !listeners[e].shouldFocusForm())
disableFocus = true;
// defer execution until after form is done rewinding
form.addDeferredRunnable(
new FormRunnable(target.getListeners().getListener(listeners[e].getMethodName()),
target,
cycle));
}
}
// Form uses cycle attributes to test whether or not to focus .
// The attribute existing at all is enough to bypass focusing.
if (disableFocus)
{
cycle.disableFocus();
}
}
void invokeComponentListeners(IComponent component, IRequestCycle cycle, BrowserEvent event)
{
String idPath = component.getExtendedId();
List listeners = getEventListeners(idPath);
if (listeners == null)
return;
for (int i = 0; i < listeners.size(); i++)
{
IComponentSpecification listener = (IComponentSpecification)listeners.get(i);
IComponent target;
ComponentEventProperty props;
if (listener.isPageSpecification())
{
target = component.getPage();
props = listener.getComponentEvents(idPath);
} else
{
target = findComponent(component.getPage().getComponents().values(), listener);
props = target.getSpecification().getComponentEvents(idPath);
}
if (props == null)
continue;
List clisteners = props.getEventListeners(event.getName());
for (int e=0; e < clisteners.size(); e++)
{
EventBoundListener eventListener = (EventBoundListener)clisteners.get(e);
_invoker.invokeListener(target.getListeners().getListener(eventListener.getMethodName()), target, cycle);
}
}
}
void invokeElementListeners(IComponent component, IRequestCycle cycle, BrowserEvent event)
{
String targetId = (String)event.getTarget().get("id");
if (targetId == null)
return;
ComponentEventProperty prop = component.getSpecification().getElementEvents(targetId);
if (prop == null)
return;
List listeners = prop.getEventListeners(event.getName());
for (int i=0; i < listeners.size(); i++)
{
EventBoundListener listener = (EventBoundListener)listeners.get(i);
_invoker.invokeListener(component.getListeners().getListener(listener.getMethodName()), component, cycle);
}
}
IComponent findComponent(Collection comps, IComponentSpecification spec)
{
IComponent ret = null;
Iterator it = comps.iterator();
while (it.hasNext())
{
IComponent comp = (IComponent)it.next();
if (comp.getSpecification().equals(spec))
{
ret = comp;
break;
}
ret = findComponent(comp.getComponents().values(), spec);
if (ret != null)
break;
}
return ret;
}
/** Local runnable for deferred form connections. */
class FormRunnable implements Runnable {
private IActionListener _listener;
private IComponent _component;
private IRequestCycle _cycle;
public FormRunnable(IActionListener listener, IComponent comp, IRequestCycle cycle)
{
_listener = listener;
_component = comp;
_cycle = cycle;
}
public void run()
{
_invoker.invokeListener(_listener, _component, _cycle);
}
}
/**
* {@inheritDoc}
*/
public void addEventListener(String componentId, IComponentSpecification listener)
{
List listeners = (List)_components.get(componentId);
if (listeners == null)
{
listeners = new ArrayList();
_components.put(componentId, listeners);
}
if (!listeners.contains(listener))
{
listeners.add(listener);
}
_propertyCache.remove(componentId);
}
/**
* {@inheritDoc}
*/
public List getEventListeners(String componentId)
{
if (componentId == null)
return null;
return (List)_components.get(componentId);
}
public ComponentEventProperty[] getEventPropertyListeners(String componentIdPath)
{
if (componentIdPath == null)
return EMPTY_PROPERTIES;
ComponentEventProperty[] ret = (ComponentEventProperty[])_propertyCache.get(componentIdPath);
if (ret != null)
return ret;
List listeners = getEventListeners(componentIdPath);
if (listeners == null || listeners.size() < 1)
return EMPTY_PROPERTIES;
List props = new ArrayList();
for (int i=0; i < listeners.size(); i++)
{
IEventListener listener = (IEventListener)listeners.get(i);
props.add(listener.getComponentEvents(componentIdPath));
}
ret = (ComponentEventProperty[])props.toArray(new ComponentEventProperty[props.size()]);
_propertyCache.put(componentIdPath, ret);
return ret;
}
/**
* {@inheritDoc}
*/
public void addFormEventListener(String formId, IComponentSpecification listener)
{
List listeners = (List)_formComponents.get(formId);
if (listeners == null)
{
listeners = new ArrayList();
_formComponents.put(formId, listeners);
}
if (!listeners.contains(listener))
{
listeners.add(listener);
}
}
/**
* {@inheritDoc}
*/
public List getFormEventListeners(String formId)
{
if (formId == null)
return null;
return (List)_formComponents.get(formId);
}
/**
* {@inheritDoc}
*/
public void resetEventDidOccur()
{
_components.clear();
_formComponents.clear();
_propertyCache.clear();
}
/** Injected. */
public void setInvoker(ListenerInvoker invoker)
{
_invoker = invoker;
}
}