/* * $RCSfile: EventSupport.java,v $ * * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.perseus.model; import java.util.Hashtable; import java.util.Vector; import org.w3c.dom.events.EventListener; /** * <code>EventSupport</code> assumes two functions. First, it offers * a central place for listeners to register for specific event types on * specific observers for a particular event propagation phase (capture * or bubble). Second, it performs the event dispatching according to the * DOM Level 2 Event model of capture, at target and bubbling. * * @version $Id: EventSupport.java,v 1.9 2006/06/29 10:47:31 ln156897 Exp $ */ class EventSupport { /** * One of the values allowed for the phase parameter * in the addEventListener method */ public static final int CAPTURE_PHASE = 0; /** * One of the values allowed for the phase parameter * in the addEventListener method */ public static final int BUBBLE_PHASE = 1; /** * Maps nodes to a map of listeners for a given event type */ protected Hashtable allListeners = new Hashtable(); /** * Temporary array used to dispatch events. */ protected EventListener[] freezeList; /** * Implementation. * * @param n the minimal size needed for the returned freezeList */ EventListener[] getFreezeList(final int n) { if (freezeList == null || freezeList.length < n) { freezeList = new EventListener[n]; } return freezeList; } /** * Removes an event listener from the input handler, for the given * input event type and phase. * * @param handler the node on which the listener was hooked * @param type the event type the listener was listening to * @param phase the phase the listener was listening to * @param listener the listener to be removed */ void removeEventListener(ModelNode handler, final String type, final int phase, final EventListener listener) { if (handler == null) { throw new NullPointerException(); } if (type == null) { throw new NullPointerException(); } if (!(phase == BUBBLE_PHASE || phase == CAPTURE_PHASE)) { throw new IllegalArgumentException(); } if (listener == null) { throw new NullPointerException(); } // See the SVG 1.1 specification, section 5.6 "The use element". An // element and all its corresponding SVGElementInstance objects share an // event listener list. if (handler instanceof ElementNodeProxy) { handler = ((ElementNodeProxy) handler).proxied; } Hashtable nodeListeners = (Hashtable) allListeners.get(handler); if (nodeListeners == null) { return; } Vector[] evtTypeListeners = (Vector[]) nodeListeners.get(type); if (evtTypeListeners == null) { return; } Vector phaseListeners = evtTypeListeners[phase]; // If phaseListeners is null, this means the listener was not registered // for that phase. According to the DOM Events Level 2 spec, just ignore // the request. if (phaseListeners != null) { phaseListeners.removeElement(listener); } } /** * Adds a event listener on the input handler for the input event type * and phase. * * @param handler the node on which the listener is hooked * @param type the type of events to listen to. Should not be null * @param phase the phase the listener listens to. Should be one * of CAPTURE_PHASE or BUBBLE_PHASE * @param listener the listener to hook to the handler */ void addEventListener(ModelNode handler, final String type, final int phase, final EventListener listener) { if (handler == null) { throw new NullPointerException(); } if (type == null) { throw new NullPointerException(); } if (!(phase == CAPTURE_PHASE || phase == BUBBLE_PHASE)) { throw new IllegalArgumentException(); } if (listener == null) { throw new NullPointerException(); } // See the SVG 1.1 specification, section 5.6 "The use element". An // element and all its corresponding SVGElementInstance objects share an // event listener list. if (handler instanceof ElementNodeProxy) { handler = ((ElementNodeProxy) handler).proxied; } Hashtable nodeListeners = (Hashtable) allListeners.get(handler); if (nodeListeners == null) { // Create entry if none exists for this node nodeListeners = new Hashtable(); allListeners.put(handler, nodeListeners); } Vector[] evtTypeListeners = (Vector[]) nodeListeners.get(type); if (evtTypeListeners == null) { // Create array for listeners of this event // type if none exists evtTypeListeners = new Vector[2]; nodeListeners.put(type, evtTypeListeners); } Vector phaseListeners = evtTypeListeners[phase]; if (phaseListeners == null) { // Create vector for listeners on that phase // if none exists phaseListeners = new Vector(1); evtTypeListeners[phase] = phaseListeners; } if (!phaseListeners.contains(listener)) { phaseListeners.addElement(listener); } } /** * Debug. Traces all the registered listeners */ /* public void dumpListeners() { Iterator iter = allListeners.keySet().iterator(); while (iter.hasNext()) { Object node = iter.next(); Hashtable nodeListeners = (Hashtable) allListeners.get(node); Iterator iter2 = nodeListeners.keySet().iterator(); System.out.println("Listeners for " + node); while (iter2.hasNext()) { Object type = iter2.next(); System.out.println("----> " + type); Vector[] evtTypeListeners = (Vector[]) nodeListeners.get(type); if (evtTypeListeners == null) { System.out.println(" +--> (null)"); } else { System.out.println(" +--> [capture]"); Vector captureListeners = evtTypeListeners[CAPTURE_PHASE]; if (captureListeners == null) { System.out.println(" +--> null"); } else { int n = captureListeners.size(); for (int i = 0; i < n; i++) { System.out.println(" +--> " + captureListeners.elementAt(i)); } } System.out.println(" +--> [bubble]"); Vector bubbleListeners = evtTypeListeners[BUBBLE_PHASE]; if (bubbleListeners == null) { System.out.println(" +--> null"); } else { int n = bubbleListeners.size(); for (int i = 0; i < n; i++) { System.out.println(" +--> " + bubbleListeners.elementAt(i)); } } } } } } */ /** * Dispatches the input event to the listeners, performing a * capture and bubble phase, as defined by the DOM Level 2 * event model. * * @param evt the event to dispatch */ public void dispatchEvent(final ModelEvent evt) { if (evt == null) { return; } // Note that an event type cannot be null, // see the Event class. String type = evt.getType(); ModelNode target = (ModelNode) evt.getTarget(); // CAPTURE PHASE // ==================================================================== // Now, perform the capture phase // We go from the root (last in the dynasty array) // to the last element (immediate parent). if (target.parent != null) { fireCapture(target.parent, evt); } // AT TARGET PHASE // ==================================================================== if (!evt.getStopPropagation()) { fireEventListeners(target, evt, CAPTURE_PHASE); fireEventListeners(target, evt, BUBBLE_PHASE); } // BUBBLE PHASE // ==================================================================== if (target.parent != null) { fireBubble(target.parent, evt); } } /** * Fires the bubble event listeners on the input target. This starts * by firing this node's listeners, then the parent bubble listeners and * so on, up to the tree's root which has no parent. * * @param currentTarget the node on which the event should be dispatched * unless the event propagation has been stopped. * @param evt the event to dispatch. */ protected void fireBubble(final ModelNode currentTarget, final ModelEvent evt) { if (evt.getStopPropagation()) { return; } fireEventListeners(currentTarget, evt, BUBBLE_PHASE); if (currentTarget.parent != null) { fireBubble(currentTarget.parent, evt); } } /** * Fires the capture event listeners on the input target. This starts * by firing the node's parent capture listeners. As a result of this * recursive behavior, the listeners on the tree root (which has no parent) * are fired first, then listeners down the tree are fired. * * @param currentTarget the node on which the event should be dispatched * unless the event propagation has been stopped. * @param evt the event to dispatch. */ protected void fireCapture(final ModelNode currentTarget, final ModelEvent evt) { if (currentTarget.parent != null) { fireCapture(currentTarget.parent, evt); } if (!evt.getStopPropagation()) { fireEventListeners(currentTarget, evt, CAPTURE_PHASE); } } /** * Fires the event listeners attached to the input current target * @param currentTarget the node on which the event is currently flowing * @param evt the event to propagate * @param phase defines whether the event is propagating in the capture * or bubble phase. One of CAPTURE_PHASE or BUBBLE_PHASE. */ protected void fireEventListeners(final ModelNode currentTarget, final ModelEvent evt, final int phase) { // Update Href. Remember that the Event's anchor is sticky and // cannot be changed once set if (evt.getAnchor() == null && currentTarget instanceof Anchor) { evt.setAnchor((Anchor) currentTarget); } Vector listeners = getEventListeners(currentTarget, phase, evt); if (listeners == null) { return; } // We use a copy of the list because event listeners may be removed // during event dispatching. int n = listeners.size(); EventListener[] freezeList = getFreezeList(n); listeners.copyInto(freezeList); evt.currentTarget = currentTarget; for (int i = 0; i < n; i++) { // The DOM Level 2 specification requires that an event listener // never be called after it has been removed. if (listeners.contains(freezeList[i])) { freezeList[i].handleEvent(evt); } } } /** * @param node the ModelNode on which listeners are seeked * @param phase the event propagation phase for which listeners are * seeked. * @param evt the event * @return a Vector of EventListener registered on the input node, * for the given phase and the given event type. */ protected Vector getEventListeners(ModelNode node, final int phase, final ModelEvent evt) { // See the SVG 1.1 specification, section 5.6 "The use element". An // element and all its corresponding SVGElementInstance objects share an // event listener list. if (node instanceof ElementNodeProxy) { node = ((ElementNodeProxy) node).proxied; } Hashtable nodeListeners = (Hashtable) allListeners.get(node); if (nodeListeners == null) { return null; } Vector[] evtTypeListeners = (Vector[]) nodeListeners.get(evt.getType()); if (evtTypeListeners == null) { return null; } return evtTypeListeners[phase]; } }