FileDocCategorySizeDatePackage
EventSupport.javaAPI DocphoneME MR2 API (J2ME)14525Wed May 02 18:00:36 BST 2007com.sun.perseus.model

EventSupport.java

/*
 *
 *
 * Copyright  1990-2007 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];
    }
}