blob: cd59f143a1aecdf3a29efb97e09f7836a0000738 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Jens Lukowski/Innoopract - initial renaming/restructuring
*
*******************************************************************************/
package org.eclipse.wst.xml.core.internal.document;
import java.util.Iterator;
import java.util.Vector;
import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier;
import org.eclipse.wst.sse.core.internal.util.Debug;
import org.eclipse.wst.xml.core.internal.Logger;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
public class XMLModelNotifierImpl implements XMLModelNotifier {
/* end: for debugging only */
private class NotifyEvent {
Object changedFeature;
boolean discarded;
Object newValue;
// note: don't initialize instance variables, since
// that causes double assignments, and lots of these are created.
INodeNotifier notifier;
Object oldValue;
int pos;
String reason;
int type;
NotifyEvent(INodeNotifier notifier, int type, Object changedFeature, Object oldValue, Object newValue, int pos) {
this.notifier = notifier;
this.type = type;
this.changedFeature = changedFeature;
this.oldValue = oldValue;
this.newValue = newValue;
this.pos = pos;
this.reason = ""; //$NON-NLS-1$
}
}
private final static String ADDED_THEN_REMOVED = "Discard: Added then removed rule"; //$NON-NLS-1$
private final static boolean fOptimizeDeferred = true;
private final static boolean fOptimizeDeferredAccordingToParentAdded = true;
private final static boolean fOptimizeDeferredAccordingToParentRemoved = true;
private final static String PARENT_IS_ADDED = "Disarded: Parent has just been added"; //$NON-NLS-1$
/* start: for debugging only */
private final static String PARENT_IS_REMOVED_TOO = "Discard: Parent was removed too"; //$NON-NLS-1$
private final static String PARENT_IS_REPARENTED = "Not Discard: Parent was removed so this implies reparenting"; //$NON-NLS-1$
private Node changedRoot = null;
private boolean changing = false;
private boolean doingNewModel = false;
private Vector events = null;
private boolean flushing = false;
/**
*/
public XMLModelNotifierImpl() {
super();
}
/**
* attrReplaced method
*
* @param element
* org.w3c.dom.Element
* @param newAttr
* org.w3c.dom.Attr
* @param oldAttr
* org.w3c.dom.Attr
*/
public void attrReplaced(Element element, Attr newAttr, Attr oldAttr) {
if (element == null)
return;
Attr attr = null;
String oldValue = null;
String newValue = null;
if (oldAttr != null) {
attr = oldAttr;
oldValue = oldAttr.getValue();
}
if (newAttr != null) {
attr = newAttr;
newValue = newAttr.getValue();
}
IDOMNode notifier = (IDOMNode) element;
int offset = notifier.getStartOffset();
notify(notifier, INodeNotifier.CHANGE, attr, oldValue, newValue, offset);
propertyChanged(notifier);
}
/**
*/
public void beginChanging() {
this.changing = true;
}
/**
*/
public void beginChanging(boolean newModel) {
beginChanging();
this.doingNewModel = newModel;
}
public void cancelPending() {
// we don't want to change the size of this array, since
// the array may be being processed, in the defferred notification
// loop, but we can signal that all
// should be discarded, so any remaining ones will be ignored.
if (this.events != null) {
Iterator iterator = this.events.iterator();
while (iterator.hasNext()) {
NotifyEvent event = (NotifyEvent) iterator.next();
event.discarded = true;
}
}
// this cancel is presumably being called as a function of
// "reinitiailization" so we can ignore changes to the
// old root, and changes to the new one will be triggered during
// reinitialization.
changedRoot = null;
}
/**
* childReplaced method
*
* @param parentNode
* org.w3c.dom.Node
* @param newChild
* org.w3c.dom.Node
* @param oldChild
* org.w3c.dom.Node
*/
public void childReplaced(Node parentNode, Node newChild, Node oldChild) {
if (parentNode == null)
return;
IDOMNode notifier = (IDOMNode) parentNode;
int type = INodeNotifier.CHANGE;
if (newChild == null)
type = INodeNotifier.REMOVE;
else if (oldChild == null)
type = INodeNotifier.ADD;
int offset = notifier.getStartOffset();
notify(notifier, type, oldChild, oldChild, newChild, offset);
structureChanged(notifier);
}
public void editableChanged(Node node) {
if (node == null)
return;
IDOMNode notifier = (IDOMNode) node;
int offset = notifier.getStartOffset();
notify(notifier, INodeNotifier.CHANGE, null, null, null, offset);
propertyChanged(notifier);
}
/**
*/
public void endChanging() {
this.doingNewModel = false;
if (!this.changing)
return; // avoid nesting calls
notifyDeferred();
if (this.changedRoot != null) {
notifyStructureChanged(this.changedRoot);
if (Debug.debugNotifyDeferred) {
String p = this.changedRoot.getNodeName();
System.out.println("Deferred STRUCUTRE_CHANGED: " + p); //$NON-NLS-1$
}
this.changedRoot = null;
}
this.changing = false;
}
/**
*/
public void endTagChanged(Element element) {
if (element == null)
return;
IDOMNode notifier = (IDOMNode) element;
int offset = notifier.getStartOffset();
notify(notifier, INodeNotifier.CHANGE, null, null, null, offset);
propertyChanged(element);
}
/**
*/
public boolean hasChanged() {
return (this.events != null);
}
/**
*/
public boolean isChanging() {
return this.changing;
}
/**
*/
private void notify(INodeNotifier notifier, int eventType, Object changedFeature, Object oldValue, Object newValue, int pos) {
if (notifier == null)
return;
if (this.changing && !this.flushing) {
// defer notification
if (this.events == null)
this.events = new Vector();
// we do not defer anything if we are doing a new Model,
// except for the document event, since all others are
// trivial and not needed at that initial point.
// But even for that one document event, in the new model case,
// it is still important to defer it.
if ((!doingNewModel) || (((Node) notifier).getNodeType() == Node.DOCUMENT_NODE)) {
this.events.addElement(new NotifyEvent(notifier, eventType, changedFeature, oldValue, newValue, pos));
}
return;
}
try {
// Its important to "keep going" if exception occurs, since this
// notification
// comes in between "about to change" and "changed" events. We do
// log, however,
// since would indicate a program error.
notifier.notify(eventType, changedFeature, oldValue, newValue, pos);
} catch (Exception e) {
Logger.logException("A structured model client threw following exception during adapter notification (" + INodeNotifier.EVENT_TYPE_STRINGS[eventType] + " )", e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
*/
private void notifyDeferred() {
if (this.events == null)
return;
if (this.flushing)
return;
this.flushing = true; // force notification
int count = this.events.size();
for (int i = 0; i < count; i++) {
NotifyEvent event = (NotifyEvent) this.events.elementAt(i);
if (event == null)
continue; // error
if (event.discarded)
continue;
if (!doingNewModel && fOptimizeDeferred) {
// check redundant events (no need to check if doing NewModel,
// since
// shouldn't be redunancies)
if (event.type == INodeNotifier.ADD) {
for (int n = i + 1; n < count; n++) {
NotifyEvent next = (NotifyEvent) this.events.elementAt(n);
if (next == null)
continue; // error
if (next.type == INodeNotifier.REMOVE && next.oldValue == event.newValue) {
// Added then removed later, discard both
event.discarded = true;
next.discarded = true;
if (Debug.debugNotifyDeferred) {
event.reason = event.reason + ADDED_THEN_REMOVED + "(see " + n + ")"; //$NON-NLS-1$ //$NON-NLS-2$
next.reason = next.reason + ADDED_THEN_REMOVED + "(see " + i + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
break;
}
}
if (event.discarded)
continue;
if (fOptimizeDeferredAccordingToParentAdded) {
for (int p = 0; p < i; p++) {
NotifyEvent prev = (NotifyEvent) this.events.elementAt(p);
if (prev == null)
continue; // error
if (prev.type == INodeNotifier.REMOVE && prev.oldValue == event.notifier) {
// parent is reparented, do not discard
if (Debug.debugNotifyDeferred) {
event.reason = event.reason + PARENT_IS_REPARENTED + "(see " + p + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
break;
} else if (prev.type == INodeNotifier.ADD && prev.newValue == event.notifier) {
// parent has been added, discard this
event.discarded = true;
if (Debug.debugNotifyDeferred) {
event.reason = event.reason + PARENT_IS_ADDED + "(see " + p + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
break;
}
}
if (event.discarded)
continue;
}
} else if (event.type == INodeNotifier.REMOVE) {
if (fOptimizeDeferredAccordingToParentRemoved) {
for (int n = i + 1; n < count; n++) {
NotifyEvent next = (NotifyEvent) this.events.elementAt(n);
if (next == null)
continue; // error
if (next.type == INodeNotifier.REMOVE) {
if (next.oldValue == event.notifier) {
// parent will be removed, discard this
event.discarded = true;
if (Debug.debugNotifyDeferred) {
event.reason = event.reason + PARENT_IS_REMOVED_TOO + "(see " + n + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
break;
}
}
}
if (event.discarded)
continue;
}
}
}
notify(event.notifier, event.type, event.changedFeature, event.oldValue, event.newValue, event.pos);
}
if (Debug.debugNotifyDeferred) {
for (int l = 0; l < count; l++) {
NotifyEvent event = (NotifyEvent) this.events.elementAt(l);
Object o = null;
String t = null;
if (event.type == INodeNotifier.ADD) {
o = event.newValue;
t = " + "; //$NON-NLS-1$
} else if (event.type == INodeNotifier.REMOVE) {
o = event.oldValue;
t = " - "; //$NON-NLS-1$
}
if (o instanceof Element) {
String p = ((Node) event.notifier).getNodeName();
String c = ((Node) o).getNodeName();
String d = (event.discarded ? "! " : " "); //$NON-NLS-1$ //$NON-NLS-2$
System.out.println(d + p + t + c);
}
}
}
this.flushing = false;
this.events = null;
}
/**
*/
private void notifyStructureChanged(Node root) {
if (root == null)
return;
INodeNotifier notifier = (INodeNotifier) root;
try {
// Its important to "keep going" if exception occurs, since this
// notification
// comes in between "about to change" and "changed" events. We do
// log, however,
// since would indicate a program error.
notifier.notify(INodeNotifier.STRUCTURE_CHANGED, null, null, null, -1);
} catch (Exception e) {
Logger.logException("A structured model client threw following exception during adapter notification (" + INodeNotifier.EVENT_TYPE_STRINGS[INodeNotifier.STRUCTURE_CHANGED] + " )", e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
*/
public void propertyChanged(Node node) {
}
/**
* @param node
*/
private void setCommonRootIfNeeded(Node node) {
// defer notification
if (this.changedRoot == null) {
this.changedRoot = node;
} else {
// tiny optimization: if previous commonAncestor (changedRoot) is
// already 'document',
// or if already equal to this 'node',
// then no need to re-calculate
if (changedRoot.getNodeType() != Node.DOCUMENT_NODE && changedRoot != node) {
Node common = ((NodeImpl) this.changedRoot).getCommonAncestor(node);
if (common != null)
this.changedRoot = common;
else
this.changedRoot = node;
}
}
}
/**
*/
public void startTagChanged(Element element) {
if (element == null)
return;
IDOMNode notifier = (IDOMNode) element;
int offset = notifier.getStartOffset();
notify(notifier, INodeNotifier.CHANGE, null, null, null, offset);
propertyChanged(element);
}
/**
*/
public void structureChanged(Node node) {
if (node == null)
return;
if (isChanging()) {
setCommonRootIfNeeded(node);
if (Debug.debugNotifyDeferred) {
String p = this.changedRoot.getNodeName();
System.out.println("requested STRUCUTRE_CHANGED: " + p); //$NON-NLS-1$
}
return;
}
if (Debug.debugNotifyDeferred) {
String p = node.getNodeName();
System.out.println("STRUCUTRE_CHANGED: " + p); //$NON-NLS-1$
}
notifyStructureChanged(node);
}
/**
* valueChanged method
*
* @param node
* org.w3c.dom.Node
*/
public void valueChanged(Node node) {
if (node == null)
return;
IDOMNode notifier = null;
if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
Attr attr = (Attr) node;
notifier = (IDOMNode) attr.getOwnerElement();
// TODO_dmw: experimental: changed 06/29/2004 to send "strucuture
// changed" even for attribute value changes
// there are pros and cons to considering attribute value
// "structure changed". Will (re)consider
// setCommonRootIfNeeded(notifier);
if (notifier == null)
return;
String value = attr.getValue();
int offset = notifier.getStartOffset();
notify(notifier, INodeNotifier.CHANGE, attr, null, value, offset);
} else {
// note: we do not send structured changed event for content
// changed
notifier = (IDOMNode) node;
String value = node.getNodeValue();
int offset = notifier.getStartOffset();
notify(notifier, INodeNotifier.CHANGE, null, null, value, offset);
if (node.getNodeType() != Node.ELEMENT_NODE) {
IDOMNode parent = (IDOMNode) node.getParentNode();
if (parent != null) {
notify(parent, INodeNotifier.CONTENT_CHANGED, node, null, value, offset);
}
}
}
propertyChanged(notifier);
}
}