/*
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.dom.events;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import org.apache.batik.dom.AbstractDocument;
import org.apache.batik.dom.AbstractNode;
import org.w3c.dom.DOMException;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventException;
import org.w3c.dom.events.EventListener;
The class allows registration and removal of EventListeners on
an NodeEventTarget and dispatch of events to that NodeEventTarget.
Author: Thierry Kormann, Stephane Hillion See Also: - NodeEventTarget
Version: $Id: EventSupport.java 1813521 2017-10-27 12:34:11Z ssteiner $
/**
* The class allows registration and removal of EventListeners on
* an NodeEventTarget and dispatch of events to that NodeEventTarget.
*
* @see NodeEventTarget
* @author <a href="mailto:Thierry.Kormann@sophia.inria.fr">Thierry Kormann</a>
* @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
* @version $Id: EventSupport.java 1813521 2017-10-27 12:34:11Z ssteiner $
*/
public class EventSupport {
The capturing listeners table.
/**
* The capturing listeners table.
*/
protected HashMap<String, EventListenerList> capturingListeners;
The bubbling listeners table.
/**
* The bubbling listeners table.
*/
protected HashMap<String, EventListenerList> bubblingListeners;
The node for which events are being handled.
/**
* The node for which events are being handled.
*/
protected AbstractNode node;
Creates a new EventSupport object.
Params: - n – the node for which events are being handled
/**
* Creates a new EventSupport object.
* @param n the node for which events are being handled
*/
public EventSupport(AbstractNode n) {
node = n;
}
This method allows the registration of event listeners on the
event target. If an EventListener
is added to an
EventTarget
which is currently processing an event
the new listener will not be triggered by the current event.
If multiple identical EventListener
s are
registered on the same EventTarget
with the same
parameters the duplicate instances are discarded. They do not
cause the EventListener
to be called twice and
since they are discarded they do not need to be removed with
the removeEventListener
method.
Params: - type – The event type for which the user is registering
- listener – The
listener
parameter takes an
interface implemented by the user which contains the methods to
be called when the event occurs. - useCapture – If true,
useCapture
indicates
that the user wishes to initiate capture. After initiating
capture, all events of the specified type will be dispatched to
the registered EventListener
before being
dispatched to any EventTargets
beneath them in the
tree. Events which are bubbling upward through the tree will
not trigger an EventListener
designated to use
capture.
/**
* This method allows the registration of event listeners on the
* event target. If an <code>EventListener</code> is added to an
* <code>EventTarget</code> which is currently processing an event
* the new listener will not be triggered by the current event.
* <br> If multiple identical <code>EventListener</code>s are
* registered on the same <code>EventTarget</code> with the same
* parameters the duplicate instances are discarded. They do not
* cause the <code>EventListener</code> to be called twice and
* since they are discarded they do not need to be removed with
* the <code>removeEventListener</code> method.
*
* @param type The event type for which the user is registering
*
* @param listener The <code>listener</code> parameter takes an
* interface implemented by the user which contains the methods to
* be called when the event occurs.
*
* @param useCapture If true, <code>useCapture</code> indicates
* that the user wishes to initiate capture. After initiating
* capture, all events of the specified type will be dispatched to
* the registered <code>EventListener</code> before being
* dispatched to any <code>EventTargets</code> beneath them in the
* tree. Events which are bubbling upward through the tree will
* not trigger an <code>EventListener</code> designated to use
* capture.
*/
public void addEventListener(String type, EventListener listener,
boolean useCapture) {
addEventListenerNS(null, type, listener, useCapture, null);
}
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) {
HashMap<String, EventListenerList> listeners;
if (useCapture) {
if (capturingListeners == null) {
capturingListeners = new HashMap();
}
listeners = capturingListeners;
} else {
if (bubblingListeners == null) {
bubblingListeners = new HashMap();
}
listeners = bubblingListeners;
}
EventListenerList list = listeners.get(type);
if (list == null) {
list = new EventListenerList();
listeners.put(type, list);
}
list.addListener(namespaceURI, group, listener);
}
This method allows the removal of event listeners from the
event target. If an EventListener
is removed from
an EventTarget
while it is processing an event, it
will complete its current actions but will not be triggered
again during any later stages of event flow.
If an
EventListener
is removed from an
EventTarget
which is currently processing an event
the removed listener will still be triggered by the current
event.
Calling removeEventListener
with
arguments which do not identify any currently registered
EventListener
on the EventTarget
has
no effect.
Params: - type – Specifies the event type of the
EventListener
being removed. - listener – The
EventListener
parameter
indicates the EventListener
to be removed. - useCapture – Specifies whether the
EventListener
being removed was registered as a
capturing listener or not. If a listener was registered twice,
one with capture and one without, each must be removed
separately. Removal of a capturing listener does not affect a
non-capturing version of the same listener, and vice versa.
/**
* This method allows the removal of event listeners from the
* event target. If an <code>EventListener</code> is removed from
* an <code>EventTarget</code> while it is processing an event, it
* will complete its current actions but will not be triggered
* again during any later stages of event flow. <br>If an
* <code>EventListener</code> is removed from an
* <code>EventTarget</code> which is currently processing an event
* the removed listener will still be triggered by the current
* event. <br>Calling <code>removeEventListener</code> with
* arguments which do not identify any currently registered
* <code>EventListener</code> on the <code>EventTarget</code> has
* no effect.
*
* @param type Specifies the event type of the
* <code>EventListener</code> being removed.
*
* @param listener The <code>EventListener</code> parameter
* indicates the <code>EventListener </code> to be removed.
*
* @param useCapture Specifies whether the
* <code>EventListener</code> being removed was registered as a
* capturing listener or not. If a listener was registered twice,
* one with capture and one without, each must be removed
* separately. Removal of a capturing listener does not affect a
* non-capturing version of the same listener, and vice versa.
*/
public void removeEventListener(String type, EventListener listener,
boolean useCapture) {
removeEventListenerNS(null, type, listener, useCapture);
}
Deregisters an event listener.
/**
* Deregisters an event listener.
*/
public void removeEventListenerNS(String namespaceURI,
String type,
EventListener listener,
boolean useCapture) {
HashMap<String, EventListenerList> listeners;
if (useCapture) {
listeners = capturingListeners;
} else {
listeners = bubblingListeners;
}
if (listeners == null) {
return;
}
EventListenerList list = listeners.get(type);
if (list != null) {
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(org.w3c.dom.Node,String,String)}.
*/
public void moveEventListeners(EventSupport other) {
other.capturingListeners = capturingListeners;
other.bubblingListeners = bubblingListeners;
capturingListeners = null;
bubblingListeners = 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 {
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
e.setTarget(target);
e.stopPropagation(false);
e.stopImmediatePropagation(false);
e.preventDefault(false);
// dump the tree hierarchy from top to the target
NodeEventTarget[] ancestors = getAncestors(target);
// CAPTURING_PHASE : fire event listeners from top to EventTarget
e.setEventPhase(Event.CAPTURING_PHASE);
HashSet stoppedGroups = new HashSet();
HashSet toBeStoppedGroups = new HashSet();
for (NodeEventTarget node : ancestors) {
e.setCurrentTarget(node);
fireEventListeners(node, e, true, stoppedGroups,
toBeStoppedGroups);
stoppedGroups.addAll(toBeStoppedGroups);
toBeStoppedGroups.clear();
}
// AT_TARGET : fire local event listeners
e.setEventPhase(Event.AT_TARGET);
e.setCurrentTarget(target);
fireEventListeners(target, e, false, stoppedGroups,
toBeStoppedGroups);
stoppedGroups.addAll(toBeStoppedGroups);
toBeStoppedGroups.clear();
// BUBBLING_PHASE : fire event listeners from target to top
if (e.getBubbles()) {
e.setEventPhase(Event.BUBBLING_PHASE);
for (int i = ancestors.length - 1; i >= 0; i--) {
NodeEventTarget node = ancestors[i];
e.setCurrentTarget(node);
fireEventListeners(node, e, false, stoppedGroups,
toBeStoppedGroups);
stoppedGroups.addAll(toBeStoppedGroups);
toBeStoppedGroups.clear();
}
}
if (!e.getDefaultPrevented()) {
runDefaultActions(e);
}
return e.getDefaultPrevented();
}
Runs all of the registered default actions for the given event object.
/**
* Runs all of the registered default actions for the given event object.
*/
protected void runDefaultActions(AbstractEvent e) {
List runables = e.getDefaultActions();
if (runables != null) {
for (Object runable : runables) {
Runnable r = (Runnable) runable;
r.run();
}
}
}
Fires the given listeners on the given event target.
/**
* Fires the given listeners on the given event target.
*/
protected void fireEventListeners(NodeEventTarget node,
AbstractEvent e,
EventListenerList.Entry[] listeners,
HashSet stoppedGroups,
HashSet toBeStoppedGroups) {
if (listeners == null) {
return;
}
// fire event listeners
String eventNS = e.getNamespaceURI();
for (EventListenerList.Entry listener : listeners) {
try {
String listenerNS = listener.getNamespaceURI();
if (listenerNS != null && eventNS != null
&& !listenerNS.equals(eventNS)) {
continue;
}
Object group = listener.getGroup();
if (stoppedGroups == null || !stoppedGroups.contains(group)) {
listener.getListener().handleEvent(e);
if (e.getStopImmediatePropagation()) {
if (stoppedGroups != null) {
stoppedGroups.add(group);
}
e.stopImmediatePropagation(false);
} else if (e.getStopPropagation()) {
if (toBeStoppedGroups != null) {
toBeStoppedGroups.add(group);
}
e.stopPropagation(false);
}
}
} catch (ThreadDeath td) {
throw td;
} catch (Throwable th) {
th.printStackTrace();
}
}
}
Fires the registered listeners on the given event target.
/**
* Fires the registered listeners on the given event target.
*/
protected void fireEventListeners(NodeEventTarget node,
AbstractEvent e,
boolean useCapture,
HashSet stoppedGroups,
HashSet toBeStoppedGroups) {
String type = e.getType();
EventSupport support = node.getEventSupport();
// check if the event support has been instantiated
if (support == null) {
return;
}
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 all ancestors of the specified node.
/**
* Returns all ancestors of the specified node.
*/
protected NodeEventTarget[] getAncestors(NodeEventTarget node) {
node = node.getParentNodeEventTarget(); // skip current node
int nancestors = 0;
for (NodeEventTarget n = node;
n != null;
n = n.getParentNodeEventTarget(), nancestors++) {
}
NodeEventTarget[] ancestors = new NodeEventTarget[nancestors];
for (int i = nancestors - 1;
i >= 0;
--i, node = node.getParentNodeEventTarget()) {
ancestors[i] = node;
}
return ancestors;
}
Returns whether this node target has an event listener for the
given event namespace URI and type.
/**
* Returns whether this node target has an event listener for the
* given event namespace URI and type.
*/
public boolean hasEventListenerNS(String namespaceURI, String type) {
if (capturingListeners != null) {
EventListenerList ell = capturingListeners.get(type);
if (ell != null) {
if (ell.hasEventListener(namespaceURI)) {
return true;
}
}
}
if (bubblingListeners != null) {
EventListenerList ell = capturingListeners.get(type);
if (ell != null) {
return ell.hasEventListener(namespaceURI);
}
}
return false;
}
Returns a list event listeners depending on the specified event
type and phase.
Params: - type – the event type
- useCapture –
/**
* Returns a list event listeners depending on the specified event
* type and phase.
* @param type the event type
* @param useCapture
*/
public EventListenerList getEventListeners(String type,
boolean useCapture) {
HashMap<String, EventListenerList> listeners
= useCapture ? capturingListeners : bubblingListeners;
if (listeners == null) {
return null;
}
return listeners.get(type);
}
Creates an EventException. Overrides this method if you need to
create your own RangeException subclass.
Params: - code – the exception code
- key – the resource key
- args – arguments to use when formatting the message
/**
* Creates an EventException. Overrides this method if you need to
* create your own RangeException subclass.
* @param code the exception code
* @param key the resource key
* @param args arguments to use when formatting the message
*/
protected EventException createEventException(short code,
String key,
Object[] args) {
try {
AbstractDocument doc = (AbstractDocument) node.getOwnerDocument();
return new EventException(code, doc.formatMessage(key, args));
} catch (Exception e) {
return new EventException(code, key);
}
}
Calls AbstractEvent.setTarget
. /**
* Calls {@link AbstractEvent#setTarget}.
*/
protected void setTarget(AbstractEvent e, NodeEventTarget target) {
e.setTarget(target);
}
/**
* Calls {@link AbstractEvent#stopPropagation(boolean)}.
*/
protected void stopPropagation(AbstractEvent e, boolean b) {
e.stopPropagation(b);
}
/**
* Calls {@link AbstractEvent#stopImmediatePropagation(boolean)}.
*/
protected void stopImmediatePropagation(AbstractEvent e, boolean b) {
e.stopImmediatePropagation(b);
}
/**
* Calls {@link AbstractEvent#preventDefault(boolean)}.
*/
protected void preventDefault(AbstractEvent e, boolean b) {
e.preventDefault(b);
}
/**
* Calls {@link AbstractEvent#setCurrentTarget}.
*/
protected void setCurrentTarget(AbstractEvent e, NodeEventTarget target) {
e.setCurrentTarget(target);
}
Calls AbstractEvent.setEventPhase
. /**
* Calls {@link AbstractEvent#setEventPhase}.
*/
protected void setEventPhase(AbstractEvent e, short phase) {
e.setEventPhase(phase);
}
Returns the ultimate original event for the given event.
/**
* Returns the ultimate original event for the given event.
*/
public static Event getUltimateOriginalEvent(Event evt) {
AbstractEvent e = (AbstractEvent) evt;
for (;;) {
AbstractEvent origEvt = (AbstractEvent) e.getOriginalEvent();
if (origEvt == null) {
break;
}
e = origEvt;
}
return e;
}
}