001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    package org.apache.tapestry.internal.event.impl;
015    
016    import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
017    import org.apache.hivemind.util.Defense;
018    import org.apache.tapestry.IActionListener;
019    import org.apache.tapestry.IComponent;
020    import org.apache.tapestry.IForm;
021    import org.apache.tapestry.IRequestCycle;
022    import org.apache.tapestry.event.BrowserEvent;
023    import org.apache.tapestry.event.ResetEventListener;
024    import org.apache.tapestry.form.FormSupport;
025    import org.apache.tapestry.internal.event.ComponentEventProperty;
026    import org.apache.tapestry.internal.event.EventBoundListener;
027    import org.apache.tapestry.internal.event.IComponentEventInvoker;
028    import org.apache.tapestry.listener.ListenerInvoker;
029    import org.apache.tapestry.spec.IComponentSpecification;
030    import org.apache.tapestry.spec.IEventListener;
031    import org.apache.tapestry.util.IdAllocator;
032    
033    import java.util.*;
034    
035    
036    /**
037     * Implementation of {@link IComponentEventInvoker}.
038     *
039     * @author jkuhnert
040     */
041    public class ComponentEventInvoker implements IComponentEventInvoker, ResetEventListener
042    {
043        static final ComponentEventProperty[] EMPTY_PROPERTIES = new ComponentEventProperty[0];
044    
045        // Mapped component id path -> List of IEventListeners
046        private Map _components = new ConcurrentHashMap();
047        // Mapped form id path -> List of IEventListeners
048        private Map _formComponents = new ConcurrentHashMap();
049        // Used to invoke actual listener methods
050        private ListenerInvoker _invoker;
051    
052        // Cached set of ComponentEventProperty[] arrays mapped to specific components
053        private Map _propertyCache = new ConcurrentHashMap();
054    
055        /**
056         * {@inheritDoc}
057         */
058        public void invokeListeners(IComponent component, IRequestCycle cycle, BrowserEvent event)
059        {
060            Defense.notNull(component, "component");
061            Defense.notNull(cycle, "cycle");
062            Defense.notNull(event, "event");
063            
064            invokeComponentListeners(component, cycle, event);
065            
066            invokeElementListeners(component, cycle, event);
067        }
068        
069        /**
070         * {@inheritDoc}
071         */
072        public void invokeFormListeners(FormSupport formSupport, final IRequestCycle cycle, final BrowserEvent event)
073        {
074            Defense.notNull(formSupport, "formSupport");
075            Defense.notNull(cycle, "cycle");
076            Defense.notNull(event, "event");
077            
078            IForm form = formSupport.getForm();
079            String formIdPath = form.getExtendedId();
080    
081            String targetId = (String)event.getTarget().get("id");
082            String strippedId = IdAllocator.convertAllocatedComponentId(targetId);
083            if (targetId == null)
084                return;
085            
086            List comps = getFormEventListeners(formIdPath);
087            if (comps == null)
088                return;
089            
090            boolean disableFocus = false;
091            
092            for (int i=0; i < comps.size(); i++) {
093                
094                IComponentSpecification spec = (IComponentSpecification)comps.get(i);
095                EventBoundListener[] listeners = spec.getFormEvents(formIdPath, event);
096                
097                IComponent target = null;
098                if (spec.isPageSpecification()) {
099                    
100                    target = form.getPage();
101                } else {
102                    
103                    target = findComponent(form.getPage().getComponents().values(), spec);
104                }
105                
106                for (int e=0; e < listeners.length; e++) {
107                    
108                    // ensure ~only~ the method that targeted this event gets called!
109                    
110                    if (!listeners[e].getComponentId().endsWith(strippedId))
111                        continue;
112    
113                    // clear validation errors but not input if async validation is
114                    // disabled
115    
116                    if (!listeners[e].isValidateForm()) {
117                        
118                        form.getDelegate().clearErrors();
119                    }
120    
121                    // handle disabling focus
122                    if (!disableFocus && !listeners[e].shouldFocusForm())
123                        disableFocus = true;
124                    
125                    // defer execution until after form is done rewinding
126                    
127                    form.addDeferredRunnable(
128                            new FormRunnable(target.getListeners().getListener(listeners[e].getMethodName()),
129                                    target,
130                                    cycle));
131                }
132            }
133            
134            // Form uses cycle attributes to test whether or not to focus .
135            // The attribute existing at all is enough to bypass focusing.
136            if (disableFocus) {
137                
138                cycle.disableFocus();
139            }
140        }
141        
142        void invokeComponentListeners(IComponent component, IRequestCycle cycle, BrowserEvent event)
143        {
144            String idPath = component.getExtendedId();
145            List listeners = getEventListeners(idPath);
146            if (listeners == null)
147                return;
148            
149            for (int i = 0; i < listeners.size(); i++) {
150                
151                IComponentSpecification listener = (IComponentSpecification)listeners.get(i);
152                
153                IComponent target = null;
154                ComponentEventProperty props = null;
155                
156                if (listener.isPageSpecification()) {
157                    
158                    target = component.getPage();
159                    props = listener.getComponentEvents(idPath);
160                } else {
161                    
162                    target = findComponent(component.getPage().getComponents().values(), listener);
163                    props = target.getSpecification().getComponentEvents(idPath);
164                }
165                if (props == null)
166                    continue;
167                
168                List clisteners = props.getEventListeners(event.getName());
169                for (int e=0; e < clisteners.size(); e++) {
170                    
171                    EventBoundListener eventListener = (EventBoundListener)clisteners.get(e);
172                    
173                    _invoker.invokeListener(target.getListeners().getListener(eventListener.getMethodName()), target, cycle);
174                }
175                
176            }
177        }
178        
179        void invokeElementListeners(IComponent component, IRequestCycle cycle, BrowserEvent event)
180        {
181            String targetId = (String)event.getTarget().get("id");
182            if (targetId == null)
183                return;
184            
185            ComponentEventProperty prop = component.getSpecification().getElementEvents(targetId);
186            if (prop == null)
187                return;
188            
189            List listeners = prop.getEventListeners(event.getName());
190            
191            for (int i=0; i < listeners.size(); i++) {
192                
193                EventBoundListener listener = (EventBoundListener)listeners.get(i);
194                
195                _invoker.invokeListener(component.getListeners().getListener(listener.getMethodName()), component, cycle);
196            }
197        }
198        
199        IComponent findComponent(Collection comps, IComponentSpecification spec)
200        {
201            IComponent ret = null;
202            
203            Iterator it = comps.iterator();
204            
205            while (it.hasNext()) {
206                IComponent comp = (IComponent)it.next();
207                
208                if (comp.getSpecification().equals(spec)) {
209                    ret = comp;
210                    break;
211                }
212                
213                ret = findComponent(comp.getComponents().values(), spec);
214                if (ret != null)
215                    break;
216            }
217            
218            return ret;
219        }
220        
221        /** Local runnable for deferred form connections. */
222        class FormRunnable implements Runnable {
223            
224            private IActionListener _listener;
225            private IComponent _component;
226            private IRequestCycle _cycle;
227            
228            public FormRunnable(IActionListener listener, IComponent comp, IRequestCycle cycle)
229            {
230                _listener = listener;
231                _component = comp;
232                _cycle = cycle;
233            }
234            
235            public void run()
236            {
237                _invoker.invokeListener(_listener, _component, _cycle);
238            }
239        }
240        
241        /**
242         * {@inheritDoc}
243         */
244        public void addEventListener(String componentId, IComponentSpecification listener)
245        {
246            List listeners = (List)_components.get(componentId);
247            
248            if (listeners == null) {
249                listeners = new ArrayList();
250                _components.put(componentId, listeners);
251            }
252            
253            if (!listeners.contains(listener)) {
254                listeners.add(listener);
255            }
256            
257            _propertyCache.remove(componentId);
258        }
259        
260        /**
261         * {@inheritDoc}
262         */
263        public List getEventListeners(String componentId)
264        {
265            if (componentId == null)
266                return null;
267            
268            return (List)_components.get(componentId);
269        }
270    
271        public ComponentEventProperty[] getEventPropertyListeners(String componentIdPath)
272        {
273            if (componentIdPath == null)
274                return EMPTY_PROPERTIES;
275            
276            ComponentEventProperty[] ret = (ComponentEventProperty[])_propertyCache.get(componentIdPath);
277            if (ret != null)
278                return ret;
279    
280            List listeners = getEventListeners(componentIdPath);
281            if (listeners == null || listeners.size() < 1)
282                return EMPTY_PROPERTIES;
283    
284            List props = new ArrayList();
285            for (int i=0; i < listeners.size(); i++) {
286    
287                IEventListener listener = (IEventListener)listeners.get(i);
288    
289                props.add(listener.getComponentEvents(componentIdPath));
290            }
291    
292            ret = (ComponentEventProperty[])props.toArray(new ComponentEventProperty[props.size()]);
293    
294            _propertyCache.put(componentIdPath, ret);
295    
296            return ret;
297        }
298    
299        /**
300         * {@inheritDoc}
301         */
302        public void addFormEventListener(String formId, IComponentSpecification listener)
303        {
304            List listeners = (List)_formComponents.get(formId);
305            
306            if (listeners == null) {
307                listeners = new ArrayList();
308                _formComponents.put(formId, listeners);
309            }
310            
311            if (!listeners.contains(listener)) {
312                listeners.add(listener);
313            }
314        }
315        
316        /**
317         * {@inheritDoc}
318         */
319        public List getFormEventListeners(String formId)
320        {
321            if (formId == null)
322                return null;
323            
324            return (List)_formComponents.get(formId);
325        }
326        
327        /**
328         * {@inheritDoc}
329         */
330        public void resetEventDidOccur()
331        {
332            _components.clear();
333            _formComponents.clear();
334            _propertyCache.clear();
335        }
336        
337        /** Injected. */
338        public void setInvoker(ListenerInvoker invoker)
339        {
340            _invoker = invoker;
341        }
342    }