/*

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You 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.batik.anim.dom;

import java.util.HashMap;
import java.util.HashSet;

import org.apache.batik.dom.AbstractNode;
import org.apache.batik.dom.events.AbstractEvent;
import org.apache.batik.dom.events.EventListenerList;
import org.apache.batik.dom.events.EventSupport;
import org.apache.batik.dom.events.NodeEventTarget;

import org.apache.batik.dom.xbl.NodeXBL;
import org.apache.batik.dom.xbl.ShadowTreeEvent;
import org.apache.batik.constants.XMLConstants;

import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventException;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.MutationEvent;

An EventSupport class that handles XBL-specific event processing.
Author:Cameron McCormack
Version:$Id: XBLEventSupport.java 1851346 2019-01-15 13:41:00Z ssteiner $
/** * An EventSupport class that handles XBL-specific event processing. * * @author <a href="mailto:cam%40mcc%2eid%2eau">Cameron McCormack</a> * @version $Id: XBLEventSupport.java 1851346 2019-01-15 13:41:00Z ssteiner $ */
public class XBLEventSupport extends EventSupport {
The unstoppable capturing listeners table.
/** * The unstoppable capturing listeners table. */
protected HashMap<String, EventListenerList> capturingImplementationListeners;
The unstoppable bubbling listeners table.
/** * The unstoppable bubbling listeners table. */
protected HashMap<String, EventListenerList> bubblingImplementationListeners;
Map of event types to their aliases.
/** * Map of event types to their aliases. */
protected static HashMap<String, String> eventTypeAliases = new HashMap<String, String>(); static { eventTypeAliases.put("SVGLoad", "load"); eventTypeAliases.put("SVGUnoad", "unload"); eventTypeAliases.put("SVGAbort", "abort"); eventTypeAliases.put("SVGError", "error"); eventTypeAliases.put("SVGResize", "resize"); eventTypeAliases.put("SVGScroll", "scroll"); eventTypeAliases.put("SVGZoom", "zoom"); }
Creates a new XBLEventSupport object.
/** * Creates a new XBLEventSupport object. */
public XBLEventSupport(AbstractNode n) { super(n); }
Registers an event listener for the given namespaced event type in the specified group.
/** * Registers an event listener for the given namespaced event type * in the specified group. */
public void addEventListenerNS(String namespaceURI, String type, EventListener listener, boolean useCapture, Object group) { super.addEventListenerNS (namespaceURI, type, listener, useCapture, group); if (namespaceURI == null || namespaceURI.equals(XMLConstants.XML_EVENTS_NAMESPACE_URI)) { String alias = eventTypeAliases.get(type); if (alias != null) { super.addEventListenerNS (namespaceURI, alias, listener, useCapture, group); } } }
Deregisters an event listener.
/** * Deregisters an event listener. */
public void removeEventListenerNS(String namespaceURI, String type, EventListener listener, boolean useCapture) { super.removeEventListenerNS(namespaceURI, type, listener, useCapture); if (namespaceURI == null || namespaceURI.equals(XMLConstants.XML_EVENTS_NAMESPACE_URI)) { String alias = eventTypeAliases.get(type); if (alias != null) { super.removeEventListenerNS (namespaceURI, alias, listener, useCapture); } } }
Registers an event listener that will not be stopped by the usual XBL stopping.
/** * Registers an event listener that will not be stopped by the usual * XBL stopping. */
public void addImplementationEventListenerNS(String namespaceURI, String type, EventListener listener, boolean useCapture) { HashMap<String, EventListenerList> listeners; if (useCapture) { if (capturingImplementationListeners == null) { capturingImplementationListeners = new HashMap<String, EventListenerList>(); } listeners = capturingImplementationListeners; } else { if (bubblingImplementationListeners == null) { bubblingImplementationListeners = new HashMap<String, EventListenerList>(); } listeners = bubblingImplementationListeners; } EventListenerList list = listeners.get(type); if (list == null) { list = new EventListenerList(); listeners.put(type, list); } list.addListener(namespaceURI, null, listener); }
Unregisters an implementation event listener.
/** * Unregisters an implementation event listener. */
public void removeImplementationEventListenerNS(String namespaceURI, String type, EventListener listener, boolean useCapture) { HashMap<String, EventListenerList> listeners = useCapture ? capturingImplementationListeners : bubblingImplementationListeners; if (listeners == null) { return; } EventListenerList list = listeners.get(type); if (list == null) { return; } list.removeListener(namespaceURI, listener); if (list.size() == 0) { listeners.remove(type); } }
Moves all of the event listeners from this EventSupport object to the given EventSupport object. Used by AbstractDocument.renameNode(Node, String, String).
/** * Moves all of the event listeners from this EventSupport object * to the given EventSupport object. * Used by {@link * org.apache.batik.dom.AbstractDocument#renameNode(Node,String,String)}. */
public void moveEventListeners(EventSupport other) { super.moveEventListeners(other); XBLEventSupport es = (XBLEventSupport) other; es.capturingImplementationListeners = capturingImplementationListeners; es.bubblingImplementationListeners = bubblingImplementationListeners; capturingImplementationListeners = null; bubblingImplementationListeners = null; }
This method allows the dispatch of events into the implementations event model. Events dispatched in this manner will have the same capturing and bubbling behavior as events dispatched directly by the implementation. The target of the event is the EventTarget on which dispatchEvent is called.
Params:
  • target – the target node
  • evt – Specifies the event type, behavior, and contextual information to be used in processing the event.
Throws:
  • EventException – UNSPECIFIED_EVENT_TYPE_ERR: Raised if the Event's type was not specified by initializing the event before dispatchEvent was called. Specification of the Event's type as null or an empty string will also trigger this exception.
Returns:The return value of dispatchEvent indicates whether any of the listeners which handled the event called preventDefault. If preventDefault was called the value is false, else the value is true.
/** * This method allows the dispatch of events into the * implementations event model. Events dispatched in this manner * will have the same capturing and bubbling behavior as events * dispatched directly by the implementation. The target of the * event is the <code> EventTarget</code> on which * <code>dispatchEvent</code> is called. * * @param target the target node * @param evt Specifies the event type, behavior, and contextual * information to be used in processing the event. * * @return The return value of <code>dispatchEvent</code> * indicates whether any of the listeners which handled the event * called <code>preventDefault</code>. If * <code>preventDefault</code> was called the value is false, else * the value is true. * * @exception EventException * UNSPECIFIED_EVENT_TYPE_ERR: Raised if the * <code>Event</code>'s type was not specified by initializing * the event before <code>dispatchEvent</code> was * called. Specification of the <code>Event</code>'s type as * <code>null</code> or an empty string will also trigger this * exception. */
public boolean dispatchEvent(NodeEventTarget target, Event evt) throws EventException { // System.err.println("\t[] dispatching " + e.getType() + " on " + ((Node) target).getNodeName()); if (evt == null) { return false; } if (!(evt instanceof AbstractEvent)) { throw createEventException (DOMException.NOT_SUPPORTED_ERR, "unsupported.event", new Object[] {}); } AbstractEvent e = (AbstractEvent) evt; String type = e.getType(); if (type == null || type.length() == 0) { throw createEventException (EventException.UNSPECIFIED_EVENT_TYPE_ERR, "unspecified.event", new Object[] {}); } // fix event status setTarget(e, target); stopPropagation(e, false); stopImmediatePropagation(e, false); preventDefault(e, false); // dump the tree hierarchy from top to the target NodeEventTarget[] ancestors = getAncestors(target); int bubbleLimit = e.getBubbleLimit(); int minAncestor = 0; if (isSingleScopeEvent(e)) { // DOM Mutation events are dispatched only within the // one shadow scope AbstractNode targetNode = (AbstractNode) target; Node boundElement = targetNode.getXblBoundElement(); if (boundElement != null) { minAncestor = ancestors.length; while (minAncestor > 0) { AbstractNode ancestorNode = (AbstractNode) ancestors[minAncestor - 1]; if (ancestorNode.getXblBoundElement() != boundElement) { break; } minAncestor--; } } } else if (bubbleLimit != 0) { // Other events may have a bubble limit (such as UI events) minAncestor = ancestors.length - bubbleLimit + 1; if (minAncestor < 0) { minAncestor = 0; } } // System.err.println("\t== ancestors:"); // for (int i = 0; i < ancestors.length; i++) { // if (i < minAncestor) { // System.err.print("\t "); // } else { // System.err.print("\t * "); // } // System.err.println(((Node) ancestors[i]).getNodeName()); // } AbstractEvent[] es = getRetargettedEvents(target, ancestors, e); boolean preventDefault = false; // CAPTURING_PHASE : fire event listeners from top to EventTarget HashSet stoppedGroups = new HashSet(); HashSet toBeStoppedGroups = new HashSet(); for (int i = 0; i < minAncestor; i++) { NodeEventTarget node = ancestors[i]; // System.err.println("\t-- CAPTURING " + e.getType() + " " + ((Node) node).getNodeName()); setCurrentTarget(es[i], node); setEventPhase(es[i], Event.CAPTURING_PHASE); fireImplementationEventListeners(node, es[i], true); } for (int i = minAncestor; i < ancestors.length; i++) { NodeEventTarget node = ancestors[i]; // System.err.println("\t-- * CAPTURING " + e.getType() + " " + ((Node) node).getNodeName()); setCurrentTarget(es[i], node); setEventPhase(es[i], Event.CAPTURING_PHASE); fireImplementationEventListeners(node, es[i], true); fireEventListeners(node, es[i], true, stoppedGroups, toBeStoppedGroups); fireHandlerGroupEventListeners(node, es[i], true, stoppedGroups, toBeStoppedGroups); preventDefault = preventDefault || es[i].getDefaultPrevented(); stoppedGroups.addAll(toBeStoppedGroups); toBeStoppedGroups.clear(); } // AT_TARGET : fire local event listeners // System.err.println("\t-- * AT_TARGET " + e.getType() + " " + ((Node) target).getNodeName()); setEventPhase(e, Event.AT_TARGET); setCurrentTarget(e, target); fireImplementationEventListeners(target, e, false); fireEventListeners(target, e, false, stoppedGroups, toBeStoppedGroups); fireHandlerGroupEventListeners(node, e, false, stoppedGroups, toBeStoppedGroups); stoppedGroups.addAll(toBeStoppedGroups); toBeStoppedGroups.clear(); preventDefault = preventDefault || e.getDefaultPrevented(); // BUBBLING_PHASE : fire event listeners from target to top if (e.getBubbles()) { for (int i = ancestors.length - 1; i >= minAncestor; i--) { NodeEventTarget node = ancestors[i]; // System.err.println("\t-- * BUBBLING " + e.getType() + " " + ((Node) node).getNodeName()); setCurrentTarget(es[i], node); setEventPhase(es[i], Event.BUBBLING_PHASE); fireImplementationEventListeners(node, es[i], false); fireEventListeners(node, es[i], false, stoppedGroups, toBeStoppedGroups); fireHandlerGroupEventListeners (node, es[i], false, stoppedGroups, toBeStoppedGroups); preventDefault = preventDefault || es[i].getDefaultPrevented(); stoppedGroups.addAll(toBeStoppedGroups); toBeStoppedGroups.clear(); } for (int i = minAncestor - 1; i >= 0; i--) { NodeEventTarget node = ancestors[i]; // System.err.println("\t-- BUBBLING " + e.getType() + " " + ((Node) node).getNodeName()); setCurrentTarget(es[i], node); setEventPhase(es[i], Event.BUBBLING_PHASE); fireImplementationEventListeners(node, es[i], false); preventDefault = preventDefault || es[i].getDefaultPrevented(); } } if (!preventDefault) { runDefaultActions(e); } return preventDefault; }
Fires the event handlers registered on an XBL 'handlerGroup' element.
/** * Fires the event handlers registered on an XBL 'handlerGroup' element. */
protected void fireHandlerGroupEventListeners(NodeEventTarget node, AbstractEvent e, boolean useCapture, HashSet stoppedGroups, HashSet toBeStoppedGroups) { // get the XBL definitions in effect for the event target NodeList defs = ((NodeXBL) node).getXblDefinitions(); for (int j = 0; j < defs.getLength(); j++) { // find the 'handlerGroup' element Node n = defs.item(j).getFirstChild(); while (n != null && !(n instanceof XBLOMHandlerGroupElement)) { n = n.getNextSibling(); } if (n == null) { continue; } node = (NodeEventTarget) n; String type = e.getType(); EventSupport support = node.getEventSupport(); // check if the event support has been instantiated if (support == null) { continue; } EventListenerList list = support.getEventListeners(type, useCapture); // check if the event listeners list is not empty if (list == null) { return; } // dump event listeners, we get the registered listeners NOW EventListenerList.Entry[] listeners = list.getEventListeners(); fireEventListeners(node, e, listeners, stoppedGroups, toBeStoppedGroups); } }
Returns whether the given event should be stopped once it crosses a shadow scope boundary.
/** * Returns whether the given event should be stopped once it crosses * a shadow scope boundary. */
protected boolean isSingleScopeEvent(Event evt) { return evt instanceof MutationEvent || evt instanceof ShadowTreeEvent; }
Returns an array of Event objects to be used for each event target in the event flow. The Event objects are retargetted if an sXBL shadow scope is crossed and the event is not a DOM mutation event.
/** * Returns an array of Event objects to be used for each event target * in the event flow. The Event objects are retargetted if an sXBL * shadow scope is crossed and the event is not a DOM mutation event. */
protected AbstractEvent[] getRetargettedEvents(NodeEventTarget target, NodeEventTarget[] ancestors, AbstractEvent e) { boolean singleScope = isSingleScopeEvent(e); AbstractNode targetNode = (AbstractNode) target; AbstractEvent[] es = new AbstractEvent[ancestors.length]; if (ancestors.length > 0) { int index = ancestors.length - 1; Node boundElement = targetNode.getXblBoundElement(); AbstractNode ancestorNode = (AbstractNode) ancestors[index]; if (!singleScope && ancestorNode.getXblBoundElement() != boundElement) { es[index] = retargetEvent(e, ancestors[index]); } else { es[index] = e; } while (--index >= 0) { ancestorNode = (AbstractNode) ancestors[index + 1]; boundElement = ancestorNode.getXblBoundElement(); AbstractNode nextAncestorNode = (AbstractNode) ancestors[index]; Node nextBoundElement = nextAncestorNode.getXblBoundElement(); if (!singleScope && nextBoundElement != boundElement) { es[index] = retargetEvent(es[index + 1], ancestors[index]); } else { es[index] = es[index + 1]; } } } return es; }
Clones and retargets the given event.
/** * Clones and retargets the given event. */
protected AbstractEvent retargetEvent(AbstractEvent e, NodeEventTarget target) { AbstractEvent clonedEvent = e.cloneEvent(); setTarget(clonedEvent, target); return clonedEvent; }
Returns the implementation listneers.
/** * Returns the implementation listneers. */
public EventListenerList getImplementationEventListeners (String type, boolean useCapture) { HashMap<String, EventListenerList> listeners = useCapture ? capturingImplementationListeners : bubblingImplementationListeners; return listeners != null ? listeners.get(type) : null; }
Fires the registered implementation listeners on the given event target.
/** * Fires the registered implementation listeners on the given event * target. */
protected void fireImplementationEventListeners(NodeEventTarget node, AbstractEvent e, boolean useCapture) { String type = e.getType(); XBLEventSupport support = (XBLEventSupport) node.getEventSupport(); // check if the event support has been instantiated if (support == null) { return; } EventListenerList list = support.getImplementationEventListeners(type, useCapture); // check if the event listeners list is not empty if (list == null) { return; } // dump event listeners, we get the registered listeners NOW EventListenerList.Entry[] listeners = list.getEventListeners(); fireEventListeners(node, e, listeners, null, null); } }