/*
* 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.ukit.dom;
import org.w3c.dom.Node;
import org.w3c.dom.Document;
import org.w3c.dom.DOMException;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.MutationEvent;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventException;
/**
* Implementation of DOM events propagation layer.
*/
public abstract class XNode
extends XBase
implements EventTarget
{
/** Event listeners registered on the node. Each event listener represented
by four objects with following offsets: 0 - EventListener object,
1 - namespace URI string, 2 - event type string, 3 - use capture phase
Boolean.FALSE or Boolean.TRUE object. */
private Object[] evtlst;
/**
* Constructs node object from its owner document.
*/
protected XNode(XDoc ownerDocument)
{
super(ownerDocument);
}
/**
* Constructs node object from other node.
*/
protected XNode(XNode node, boolean deep)
{
super(node, deep);
}
/**
* Constructs node object from its qualified name and namespace URI and
* its owner document.
*/
protected XNode(String namespaceURI, String qName, XDoc ownerDocument)
{
super(namespaceURI, qName, ownerDocument);
}
/**
* A code representing the type of the underlying object, as defined above.
*/
public abstract short getNodeType();
/**
* This method allows the registration of event listeners on the event
* target. If an <code>EventListener</code> is added to an
* <code>EventTarget</code> while it is processing an event, it will not
* be triggered by the current actions but may be triggered during a
* later stage of event flow, such as the bubbling phase.
* <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);
}
/**
* 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 not
* be triggered by the current actions. <code>EventListener</code>s can
* never be invoked after being removed.
* <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);
}
/**
* This method allows the registration of an event listener in a
* specified group or the default group and, depending on the
* <code>useCapture</code> parameter, on the capture phase of the DOM
* event flow or its target and bubbling phases.
*
* @param namespaceURI Specifies the <code>Event.namespaceURI</code>
* associated with the event for which the user is registering.
* @param type Refer to the <code>EventTarget.addEventListener()</code>
* method for a description of this parameter.
* @param listener Refer to the
* <code>EventTarget.addEventListener()</code> method for a
* description of this parameter.
* @param useCapture Refer to the
* <code>EventTarget.addEventListener()</code> method for a
* description of this parameter.
*
* @since DOM Level 3
*/
public void addEventListenerNS(String namespaceURI, String type,
EventListener listener, boolean useCapture)
{
if (type == null || type.length() == 0 || listener == null)
return;
int idx = _getIndex(listener, namespaceURI, type, useCapture);
if (idx >= 0) // there is already such listener in the list
return;
idx = (evtlst != null)? evtlst.length: 0;
Object list[] = new Object[idx + (1 << 2)];
if (evtlst != null)
System.arraycopy(evtlst, 0, list, 0, idx);
list[idx] = listener;
list[idx + 1] = _getDoc()._intern(namespaceURI);
list[idx + 2] = _getDoc()._intern(type);
list[idx + 3] = (useCapture == true)? Boolean.TRUE: Boolean.FALSE;
evtlst = list;
}
/**
* This method allows the removal of an event listener, independently of
* the associated event group. Calling <code>removeEventListenerNS</code>
* with arguments which do not identify any currently registered
* <code>EventListener</code> on the <code>EventTarget</code> has no
* effect.
*
* @param namespaceURI Specifies the <code>Event.namespaceURI</code>
* associated with the event for which the user registered the event
* listener.
* @param type Refer to the
* <code>EventTarget.removeEventListener()</code> method for a
* description of this parameter.
* @param listener Refer to the
* <code>EventTarget.removeEventListener()</code> method for a
* description of this parameter.
* @param useCapture Refer to the
* <code>EventTarget.removeEventListener()</code> method for a
* description of this parameter.
*
* @since DOM Level 3
*/
public void removeEventListenerNS(String namespaceURI, String type,
EventListener listener, boolean useCapture)
{
if (type == null || type.length() == 0 || listener == null)
return;
int idx = _getIndex(listener, namespaceURI, type, useCapture);
if (idx < 0) // there is no such listener in the list
return;
if (evtlst.length == (1 << 2)) {
// There is only one listener in the list
evtlst = null;
return;
}
int len = evtlst.length - (1 << 2);
Object list[] = new Object[len];
if (idx == 0) { // the first
System.arraycopy(evtlst, (1 << 2), list, 0, len);
} else if ((idx << 2) == evtlst.length) { // the last
System.arraycopy(evtlst, 0, list, 0, len);
} else {
System.arraycopy(evtlst, 0, list, 0, idx << 2);
System.arraycopy(evtlst, ((idx + 1) << 2), list, idx << 2,
evtlst.length - ((idx + 1) << 2));
}
evtlst = list;
}
/**
* This method allows the dispatch of events into the implementation's
* event model. The event target of the event is the
* <code>EventTarget</code> object on which <code>dispatchEvent</code>
* is called.
*
* @param evt The event to be dispatched.
* @return Indicates whether any of the listeners which handled the
* event called <code>Event.preventDefault()</code>. If
* <code>Event.preventDefault()</code> was called the returned value
* is <code>false</code>, else it is <code>true</code>.
* @exception EventException
* UNSPECIFIED_EVENT_TYPE_ERR: Raised if the <code>Event.type</code>
* was not specified by initializing the event before
* <code>dispatchEvent</code> was called. Specification of the
* <code>Event.type</code> as <code>null</code> or an empty string
* will also trigger this exception.
* <br> DISPATCH_REQUEST_ERR: Raised if the <code>Event</code> object is
* already being dispatched.
* @exception DOMException
* NOT_SUPPORTED_ERR: Raised if the <code>Event</code> object has not
* been created using <code>DocumentEvent.createEvent()</code>.
* <br> INVALID_CHARACTER_ERR: Raised if <code>Event.type</code> is not
* an
* <a href='http://www.w3.org/TR/2004/REC-xml-names11-20040204/#NT-NCName'>
* NCName</a> as defined in [
* <a href='http://www.w3.org/TR/2004/REC-xml-names11-20040204/'>
* XML Namespaces 1.1</a>].
*
* @since DOM Level 3
*/
public boolean dispatchEvent(Event evt)
throws EventException, DOMException
{
XEvt xevt;
try {
xevt = (XEvt)evt;
} catch (ClassCastException cce) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "");
}
xevt._canDispatch(this);
XNode._checkName(xevt.getType(), false);
xevt._setTarget(this);
XList list = (_getParent() != null)? _getParent()._getPropChain(): null;
// Capturing phase
if (list != null && xevt._setPhase(Event.CAPTURING_PHASE)) {
for (int idx = 0; idx < list.getLength(); idx++)
((XNode)(list.item(idx)))._dispatchEvt(xevt);
}
// Target phase
if (xevt._setPhase(Event.AT_TARGET)) {
xevt._setCurrentTarget(this);
_dispatchEvt(xevt);
}
// Bubbling phase
if (list != null && xevt._setPhase(Event.BUBBLING_PHASE)) {
for (int idx = list.getLength() - 1; idx >= 0; idx--)
((XNode)(list.item(idx)))._dispatchEvt(xevt);
}
if (list != null)
((XEvtDoc)_getDoc())._free(list);
xevt._setPhase((short)-1);
return !xevt.getDefaultPrevented();
}
/**
* Dispatches an event to all listeners on this node.
*
* @param xevt Event object to dispatch.
*/
private final void _dispatchEvt(XEvt xevt)
{
if (evtlst == null || xevt._isStopped())
return;
Object list[] = evtlst; // keep a copy of initial set of listeners
int length = list.length >> 2; // number of listeners
xevt._setCurrentTarget(this);
// Dispatch event to all listeners
listeners: for (int idx = 0; idx < length; idx++) {
int base = idx << 2;
switch (xevt.getEventPhase()) {
case Event.CAPTURING_PHASE:
if (list[base + 3] == Boolean.FALSE) // useCapture == false
continue listeners;
break;
case Event.BUBBLING_PHASE:
case Event.AT_TARGET:
if (list[base + 3] != Boolean.FALSE) // useCapture == true
continue listeners;
break;
}
if (xevt.getNamespaceURI() != list[base + 1] ||
xevt.getType() != list[base + 2])
continue listeners;
try {
((EventListener)(list[base])).handleEvent(xevt);
} catch (Throwable t) {
}
}
}
/**
* Retrieves event propagation chain of nodes. Note, _getParent for an
* attribute node returns element node. If an event is dispatched on a
* disconnected subtree, the root node (#1.2.1) is the topmost node which
* has not parent but not Document node.
*
* @return A list which contains event propagation chain.
*/
/* pkg */ final XList _getPropChain()
{
XList list = (_getParent() != null)?
_getParent()._getPropChain(): ((XEvtDoc)_getDoc())._getList();
list._append(this);
return list;
}
/**
* Retrieves an index of the listener in the registered listeners list.
*
* @return An index of the listener or -1 if this listener is not registered.
*/
/* pkg */ final int _getIndex(EventListener listener,
String namespaceURI, String type, boolean useCapture)
{
int length = (evtlst != null)? evtlst.length >> 2: 0;
for (int idx = 0; idx < length; idx++) {
if (evtlst[idx << 2] == listener) {
int base = idx << 2;
if (!((String)(evtlst[base + 2])).equals(type))
continue;
if (((Boolean)(evtlst[base + 3])).booleanValue() != useCapture)
continue;
if (namespaceURI == null && namespaceURI != evtlst[base + 1])
continue;
if (namespaceURI != null &&
!namespaceURI.equals((String)(evtlst[base + 1])))
continue;
return idx;
}
}
return -1;
}
/**
* Notification of child addition.
*/
protected void _childAdded(XNode child)
{
XEvtDoc doc = (XEvtDoc)_getDoc();
if ((doc.flags & XDoc.FLAG_BUILD) != 0)
return;
MutationEvent evt = (MutationEvent)doc.createEvent("MutationEvent");
evt.initMutationEventNS(null, XEvtDoc.ET_UNINS, true, false,
this, null, null, null, (short)0);
child.dispatchEvent(evt);
evt = (MutationEvent)doc.createEvent("MutationEvent");
evt.initMutationEventNS(null, XEvtDoc.ET_UTREE, true, false,
null, null, null, null, (short)0);
dispatchEvent(evt);
}
/**
* Notification of child removal.
*/
protected void _childRemoving(XNode child)
{
XEvtDoc doc = (XEvtDoc)_getDoc();
if ((doc.flags & XDoc.FLAG_BUILD) != 0)
return;
MutationEvent evt = (MutationEvent)doc.createEvent("MutationEvent");
evt.initMutationEventNS(null, XEvtDoc.ET_UNRM, true, false,
this, null, null, null, (short)0);
child.dispatchEvent(evt);
evt = (MutationEvent)doc.createEvent("MutationEvent");
evt.initMutationEventNS(null, XEvtDoc.ET_UTREE, true, false,
null, null, null, null, (short)0);
dispatchEvent(evt);
}
/**
* Notification of a child have been removed.
*/
protected void _childRemoved(XNode child)
{
}
/**
* Notification of attribute added.
*/
protected void _attrAdded(String name, String value)
{
XEvtDoc doc = (XEvtDoc)_getDoc();
if ((doc.flags & XDoc.FLAG_BUILD) != 0)
return;
MutationEvent evt = (MutationEvent)doc.createEvent("MutationEvent");
evt.initMutationEventNS(null, XEvtDoc.ET_UAMOD, true, false,
null, name, null, value, MutationEvent.ADDITION);
dispatchEvent(evt);
evt = (MutationEvent)doc.createEvent("MutationEvent");
evt.initMutationEventNS(null, XEvtDoc.ET_UTREE, true, false,
null, null, null, null, (short)0);
dispatchEvent(evt);
}
/**
* Notification of attribute change.
*/
protected void _attrChanged(String name, String oldValue, String newValue)
{
XEvtDoc doc = (XEvtDoc)_getDoc();
if ((doc.flags & XDoc.FLAG_BUILD) != 0)
return;
MutationEvent evt = (MutationEvent)doc.createEvent("MutationEvent");
evt.initMutationEventNS(null, XEvtDoc.ET_UAMOD, true, false,
null, name, oldValue, newValue, MutationEvent.MODIFICATION);
dispatchEvent(evt);
evt = (MutationEvent)doc.createEvent("MutationEvent");
evt.initMutationEventNS(null, XEvtDoc.ET_UTREE, true, false,
null, null, null, null, (short)0);
dispatchEvent(evt);
}
/**
* Notification of attribute removed.
*/
protected void _attrRemoved(String name, String value)
{
XEvtDoc doc = (XEvtDoc)_getDoc();
if ((doc.flags & XDoc.FLAG_BUILD) != 0)
return;
MutationEvent evt = (MutationEvent)doc.createEvent("MutationEvent");
evt.initMutationEventNS(null, XEvtDoc.ET_UAMOD, true, false,
null, name, value, null, MutationEvent.REMOVAL);
dispatchEvent(evt);
evt = (MutationEvent)doc.createEvent("MutationEvent");
evt.initMutationEventNS(null, XEvtDoc.ET_UTREE, true, false,
null, null, null, null, (short)0);
dispatchEvent(evt);
}
/**
* Notification of data change.
*/
protected void _dataChanged(String oldData, String newData)
{
XEvtDoc doc = (XEvtDoc)_getDoc();
if ((doc.flags & XDoc.FLAG_BUILD) != 0)
return;
MutationEvent evt = (MutationEvent)doc.createEvent("MutationEvent");
evt.initMutationEventNS(null, XEvtDoc.ET_UCMOD, true, false,
this, oldData, newData, null, (short)0);
dispatchEvent(evt);
if (_getParent() != null) {
evt = (MutationEvent)doc.createEvent("MutationEvent");
evt.initMutationEventNS(null, XEvtDoc.ET_UTREE, true, false,
null, null, null, null, (short)0);
_getParent().dispatchEvent(evt);
}
}
}