/*******************************************************************************
 * Copyright (c) 2001, 2018 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     
 *******************************************************************************/
package org.eclipse.wst.xml.core.internal.document;

import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.model.AbstractStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.events.IStructuredDocumentListener;
import org.eclipse.wst.sse.core.internal.provisional.events.NewDocumentEvent;
import org.eclipse.wst.sse.core.internal.provisional.events.NoChangeEvent;
import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent;
import org.eclipse.wst.sse.core.internal.provisional.events.RegionsReplacedEvent;
import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentRegionsReplacedEvent;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegionList;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.xml.core.internal.Logger;
import org.eclipse.wst.xml.core.internal.provisional.IXMLNamespace;
import org.eclipse.wst.xml.core.internal.provisional.NameValidator;
import org.eclipse.wst.xml.core.internal.provisional.contenttype.ContentTypeIdForXML;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.eclipse.wst.xml.core.internal.provisional.document.ISourceGenerator;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Node;


/**
 * XMLModelImpl class
 */
public class DOMModelImpl extends AbstractStructuredModel implements IStructuredDocumentListener, IDOMModel, DOMImplementation {
	private static String TRACE_PARSER_MANAGEMENT_EXCEPTION = "parserManagement"; //$NON-NLS-1$
	private Object active = null;
	private DocumentImpl document = null;
	private ISourceGenerator generator = null;
	private XMLModelNotifier notifier = null;
	private XMLModelParser parser = null;
	private boolean refresh = false;
	private XMLModelUpdater updater = null;

	/**
	 * XMLModelImpl constructor
	 */
	public DOMModelImpl() {
		super();
		this.document = (DocumentImpl) internalCreateDocument();
	}

	/**
	 * This API allows clients to declare that they are about to make a
	 * "large" change to the model. This change might be in terms of content
	 * or it might be in terms of the model id or base location.
	 * 
	 * Note that in the case of embedded calls, notification to listeners is
	 * sent only once.
	 * 
	 * Note that the client who is making these changes has the responsibility
	 * to restore the models state once finished with the changes. See
	 * getMemento and restoreState.
	 * 
	 * The method isModelStateChanging can be used by a client to determine if
	 * the model is already in a change sequence.
	 */
	public void aboutToChangeModel() {
		super.aboutToChangeModel();
		// technically, no need to call beginChanging so often,
		// since aboutToChangeModel can be nested.
		// but will leave as is for this release.
		// see modelChanged, and be sure stays coordinated there.
		getModelNotifier().beginChanging();
	}

	public void aboutToReinitializeModel() {
		XMLModelNotifier notifier = getModelNotifier();
		notifier.cancelPending();
		super.aboutToReinitializeModel();
	}

	/**
	 * attrReplaced method
	 * 
	 * @param element
	 *            org.w3c.dom.Element
	 * @param newAttr
	 *            org.w3c.dom.Attr
	 * @param oldAttr
	 *            org.w3c.dom.Attr
	 */
	protected void attrReplaced(Element element, Attr newAttr, Attr oldAttr) {
		if (element == null)
			return;
		if (getActiveParser() == null) {
			XMLModelUpdater updater = getModelUpdater();
			setActive(updater);
			updater.initialize();
			updater.replaceAttr(element, newAttr, oldAttr);
			setActive(null);
		}
		getModelNotifier().attrReplaced(element, newAttr, oldAttr);
	}

	/**
	 * This API allows a client controlled way of notifying all ModelEvent
	 * listners that the model has been changed. This method is a matched pair
	 * to aboutToChangeModel, and must be called after aboutToChangeModel ...
	 * or some listeners could be left waiting indefinitely for the changed
	 * event. So, its suggested that changedModel always be in a finally
	 * clause. Likewise, a client should never call changedModel without
	 * calling aboutToChangeModel first.
	 * 
	 * In the case of embedded calls, the notification is just sent once.
	 * 
	 */
	public void changedModel() {
		// NOTE: the order of 'changedModel' and 'endChanging' is significant.
		// By calling changedModel first, this basically decrements the
		// "isChanging" counter
		// in super class and when zero all listeners to model state events
		// will be notified
		// that the model has been changed. 'endChanging' will notify all
		// deferred adapters.
		// So, the significance of order is that adapters (and methods they
		// call)
		// can count on the state of model "isChanging" to be accurate.
		// But, remember, that this means the "modelChanged" event can be
		// received before all
		// adapters have finished their processing.
		// NOTE NOTE: The above note is obsolete in fact (though still states
		// issue correctly).
		// Due to popular demand, the order of these calls were reversed and
		// behavior
		// changed on 07/22/2004.
		// 
		// see also
		// https://w3.opensource.ibm.com/bugzilla/show_bug.cgi?id=4302
		// for motivation for this 'on verge of' call.
		// this could be improved in future if notifier also used counting
		// flag to avoid nested calls. If/when changed be sure to check if
		// aboutToChangeModel needs any changes too.
		if (isModelChangeStateOnVergeOfEnding()) {
			// end lock before noticiation loop, since directly or indirectly
			// we may be "called from foriegn code" during notification.
			endLock();
			// we null out here to avoid spurious"warning" message while debug
			// tracing is enabled
			fLockObject = null;
			// the notifier is what controls adaper notification, which
			// should be sent out before the 'modelChanged' event.
			getModelNotifier().endChanging();
		}
		// changedModel handles 'nesting', so only one event sent out
		// when mulitple calls to 'aboutToChange/Changed'.
		super.changedModel();
		handleRefresh();
	}

	/**
	 * childReplaced method
	 * 
	 * @param parentNode
	 *            org.w3c.dom.Node
	 * @param newChild
	 *            org.w3c.dom.Node
	 * @param oldChild
	 *            org.w3c.dom.Node
	 */
	protected void childReplaced(Node parentNode, Node newChild, Node oldChild) {
		if (parentNode == null)
			return;
		if (getActiveParser() == null) {
			XMLModelUpdater updater = getModelUpdater();
			setActive(updater);
			updater.initialize();
			updater.replaceChild(parentNode, newChild, oldChild);
			setActive(null);
		}
		getModelNotifier().childReplaced(parentNode, newChild, oldChild);
	}

	/**
	 * Creates an XML <code>Document</code> object of the specified type
	 * with its document element. HTML-only DOM implementations do not need to
	 * implement this method.
	 * 
	 * @param namespaceURIThe
	 *            namespace URI of the document element to create.
	 * @param qualifiedNameThe
	 *            qualified name of the document element to be created.
	 * @param doctypeThe
	 *            type of document to be created or <code>null</code>. When
	 *            <code>doctype</code> is not <code>null</code>, its
	 *            <code>Node.ownerDocument</code> attribute is set to the
	 *            document being created.
	 * @return A new <code>Document</code> object.
	 * @exception DOMException
	 *                INVALID_CHARACTER_ERR: Raised if the specified qualified
	 *                name contains an illegal character. <br>
	 *                NAMESPACE_ERR: Raised if the <code>qualifiedName</code>
	 *                is malformed, if the <code>qualifiedName</code> has a
	 *                prefix and the <code>namespaceURI</code> is
	 *                <code>null</code>, or if the
	 *                <code>qualifiedName</code> has a prefix that is "xml"
	 *                and the <code>namespaceURI</code> is different from "
	 *                http://www.w3.org/XML/1998/namespace" .<br>
	 *                WRONG_DOCUMENT_ERR: Raised if <code>doctype</code> has
	 *                already been used with a different document or was
	 *                created from a different implementation.
	 * @see DOM Level 2
	 */
	public Document createDocument(String namespaceURI, String qualifiedName, DocumentType doctype) throws DOMException {
		final DocumentImpl document = new DocumentImpl();
		if (namespaceURI == null && qualifiedName == null && doctype == null)
			return document;

		if (qualifiedName != null) {

			final int idx = qualifiedName.indexOf(':');
			if (idx > 0) {
				if (namespaceURI == null)
					throw new DOMException(DOMException.NAMESPACE_ERR, null);
				final String prefix = qualifiedName.substring(0, idx);
				if (prefix.equals(IXMLNamespace.XML) && !namespaceURI.equals(IXMLNamespace.XML_URI))
					throw new DOMException(DOMException.NAMESPACE_ERR, null);

				// Check if the qualifiedName is malformed
				if (idx == qualifiedName.length() - 1) // No local name
					throw new DOMException(DOMException.NAMESPACE_ERR, null);

				String localName = qualifiedName.substring(idx + 1);
				final int length = localName.length();
				if (length == 0)
					throw new DOMException(DOMException.NAMESPACE_ERR, null);

				switch (localName.charAt(0)) {
					case '-':
					case '.':
						throw new DOMException(DOMException.NAMESPACE_ERR, null);
				}

				final int qualifiedLength = qualifiedName.length();
				for (int i = 0; i < qualifiedLength; i++) {
					final char c = qualifiedName.charAt(i);
					if (Character.isWhitespace(c))
						throw new DOMException(DOMException.INVALID_CHARACTER_ERR, null);
					else if (c == ':' && i != idx)
						throw new DOMException(DOMException.NAMESPACE_ERR, null);
				}
			}
			if (!NameValidator.isValid(qualifiedName))
				throw new DOMException(DOMException.INVALID_CHARACTER_ERR, null);
		}
		else if (namespaceURI != null){
				throw new DOMException(DOMException.NAMESPACE_ERR, null);
		}

		final DOMModelImpl model = (DOMModelImpl) StructuredModelManager.getModelManager().createUnManagedStructuredModelFor(ContentTypeIdForXML.ContentTypeID_XML);
		if (model != null) {
			document.setModel(model);
			model.document = document;
		}

		if (doctype != null) {
			if (doctype.getOwnerDocument() != null)
				throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
			document.appendChild(doctype);
		}

		final ElementImpl root = new ElementImpl();
		document.appendChild(root);
		root.setNamespaceURI(namespaceURI);
		root.setTagName(qualifiedName);

		return document;
	}

	/**
	 * Creates an empty <code>DocumentType</code> node. Entity declarations
	 * and notations are not made available. Entity reference expansions and
	 * default attribute additions do not occur. It is expected that a future
	 * version of the DOM will provide a way for populating a
	 * <code>DocumentType</code>.<br>
	 * HTML-only DOM implementations do not need to implement this method.
	 * 
	 * @param qualifiedNameThe
	 *            qualified name of the document type to be created.
	 * @param publicIdThe
	 *            external subset public identifier.
	 * @param systemIdThe
	 *            external subset system identifier.
	 * @return A new <code>DocumentType</code> node with
	 *         <code>Node.ownerDocument</code> set to <code>null</code>.
	 * @exception DOMException
	 *                INVALID_CHARACTER_ERR: Raised if the specified qualified
	 *                name contains an illegal character. <br>
	 *                NAMESPACE_ERR: Raised if the <code>qualifiedName</code>
	 *                is malformed.
	 * @see DOM Level 2
	 */
	public DocumentType createDocumentType(String qualifiedName, String publicId, String systemId) throws DOMException {
		DocumentTypeImpl documentType = new DocumentTypeImpl();
		documentType.setName(qualifiedName);
		documentType.setPublicId(publicId);
		documentType.setSystemId(systemId);
		return documentType;
	}

	/**
	 */
	protected void documentTypeChanged() {
		if (this.refresh)
			return;
		// unlike 'resfresh', 'reinitialize' finishes loop
		// and flushes remaining notification que before
		// actually reinitializing.
		// ISSUE: should reinit be used instead of handlerefresh?
		// this.setReinitializeNeeded(true);
		if (this.active != null || getModelNotifier().isChanging())
			return; // defer
		handleRefresh();
	}

	protected void editableChanged(Node node) {
		if (node != null) {
			getModelNotifier().editableChanged(node);
		}
	}

	/**
	 */
	protected void endTagChanged(Element element) {
		if (element == null)
			return;
		if (getActiveParser() == null) {
			XMLModelUpdater updater = getModelUpdater();
			setActive(updater);
			updater.initialize();
			updater.changeEndTag(element);
			setActive(null);
		}
		getModelNotifier().endTagChanged(element);
	}

	/**
	 */
	private XMLModelParser getActiveParser() {
		if (this.parser == null)
			return null;
		if (this.parser != this.active)
			return null;
		return this.parser;
	}

	/**
	 */
	private XMLModelUpdater getActiveUpdater() {
		if (this.updater == null)
			return null;
		if (this.updater != this.active)
			return null;
		return this.updater;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
	 */
	public Object getAdapter(Class adapter) {
		if (Document.class.equals(adapter))
			return getDocument();
		return super.getAdapter(adapter);
	}

	/**
	 * getDocument method
	 * 
	 * @return XMLDocument
	 */
	public IDOMDocument getDocument() {
		return this.document;
	}

	public ISourceGenerator getGenerator() {
		if (this.generator == null) {
			this.generator = XMLGeneratorImpl.getInstance();
		}
		return this.generator;
	}

	/**
	 * getNode method
	 * 
	 * @param offset
	 *            int
	 */
	public IndexedRegion getIndexedRegion(int offset) {
		if (this.document == null)
			return null;
		// search in document children
		IDOMNode parent = null;
		int length = this.document.getEndOffset();
		if (offset * 2 < length) {
			// search from the first
			IDOMNode child = (IDOMNode) this.document.getFirstChild();
			while (child != null) {
				if (child.getEndOffset() <= offset) {
					child = (IDOMNode) child.getNextSibling();
					continue;
				}
				if (child.getStartOffset() > offset) {
					break;
				}
				IStructuredDocumentRegion startStructuredDocumentRegion = child.getStartStructuredDocumentRegion();
				if (startStructuredDocumentRegion != null) {
					if (startStructuredDocumentRegion.getEnd() > offset)
						return child;
				}
				IStructuredDocumentRegion endStructuredDocumentRegion = child.getEndStructuredDocumentRegion();
				if (endStructuredDocumentRegion != null) {
					if (endStructuredDocumentRegion.getStart() <= offset)
						return child;
				}
				// dig more
				parent = child;
				child = (IDOMNode) parent.getFirstChild();
			}
		}
		else {
			// search from the last
			IDOMNode child = (IDOMNode) this.document.getLastChild();
			while (child != null) {
				if (child.getStartOffset() > offset) {
					child = (IDOMNode) child.getPreviousSibling();
					continue;
				}
				if (child.getEndOffset() <= offset) {
					break;
				}
				IStructuredDocumentRegion startStructuredDocumentRegion = child.getStartStructuredDocumentRegion();
				if (startStructuredDocumentRegion != null) {
					if (startStructuredDocumentRegion.getEnd() > offset)
						return child;
				}
				IStructuredDocumentRegion endStructuredDocumentRegion = child.getEndStructuredDocumentRegion();
				if (endStructuredDocumentRegion != null) {
					if (endStructuredDocumentRegion.getStart() <= offset)
						return child;
				}
				// dig more
				parent = child;
				child = (IDOMNode) parent.getLastChild();
			}
		}
		return parent;
	}

	/**
	 */
	public XMLModelNotifier getModelNotifier() {
		if (this.notifier == null) {
			this.notifier = new XMLModelNotifierImpl();
		}
		return this.notifier;
	}

	/**
	 */
	private XMLModelParser getModelParser() {
		if (this.parser == null) {
			this.parser = createModelParser();
		}
		return this.parser;
	}

	protected XMLModelParser createModelParser() {
		return new XMLModelParser(this);
	}

	/**
	 */
	private XMLModelUpdater getModelUpdater() {
		if (this.updater == null) {
			this.updater = createModelUpdater();
		}
		return this.updater;
	}

	protected XMLModelUpdater createModelUpdater() {
		return new XMLModelUpdater(this);
	}

	/**
	 */
	private void handleRefresh() {
		if (!this.refresh)
			return;
		XMLModelNotifier notifier = getModelNotifier();
		boolean isChanging = notifier.isChanging();
		if (!isChanging)
			notifier.beginChanging(true);
		XMLModelParser parser = getModelParser();
		setActive(parser);
		this.document.removeChildNodes();
		try {
			this.refresh = false;
			parser.replaceStructuredDocumentRegions(getStructuredDocument().getRegionList(), null);
		}
		catch (DOMException dex) {
			// this resulted from a text change, do not log it
		}
		catch (Exception ex) {
			Logger.logException(ex);
		}
		finally {
			setActive(null);
			if (!isChanging)
				notifier.endChanging();
		}
	}

	/**
	 * Test if the DOM implementation implements a specific feature.
	 * 
	 * @param featureThe
	 *            name of the feature to test (case-insensitive). The values
	 *            used by DOM features are defined throughout the DOM Level 2
	 *            specifications and listed in the section. The name must be
	 *            an XML name. To avoid possible conflicts, as a convention,
	 *            names referring to features defined outside the DOM
	 *            specification should be made unique by reversing the name of
	 *            the Internet domain name of the person (or the organization
	 *            that the person belongs to) who defines the feature,
	 *            component by component, and using this as a prefix. For
	 *            instance, the W3C SVG Working Group defines the feature
	 *            "org.w3c.dom.svg".
	 * @param versionThis
	 *            is the version number of the feature to test. In Level 2,
	 *            the string can be either "2.0" or "1.0". If the version is
	 *            not specified, supporting any version of the feature causes
	 *            the method to return <code>true</code>.
	 * @return <code>true</code> if the feature is implemented in the
	 *         specified version, <code>false</code> otherwise.
	 */
	public boolean hasFeature(String feature, String version) {
		if (feature == null)
			return false;
		if (version != null) {
			if (!version.equals("1.0") && !version.equals("2.0")) { //$NON-NLS-2$//$NON-NLS-1$
				return false;
			}
		}
		if (feature.equalsIgnoreCase("Core")) //$NON-NLS-1$
			return true; //$NON-NLS-1$
		if (feature.equalsIgnoreCase("XML")) //$NON-NLS-1$
			return true; //$NON-NLS-1$
		return false;
	}

	/**
	 * createDocument method
	 * 
	 * @return org.w3c.dom.Document
	 */
	protected Document internalCreateDocument() {
		DocumentImpl document = new DocumentImpl();
		document.setModel(this);
		return document;
	}

	boolean isReparsing() {
		return (active != null);
	}

	/**
	 * nameChanged method
	 * 
	 * @param node
	 *            org.w3c.dom.Node
	 */
	protected void nameChanged(Node node) {
		if (node == null)
			return;
		if (getActiveParser() == null) {
			XMLModelUpdater updater = getModelUpdater();
			setActive(updater);
			updater.initialize();
			updater.changeName(node);
			setActive(null);
		}
		// notification is already sent
	}

	/**
	 * newModel method
	 * 
	 */
	public void newModel(NewDocumentEvent structuredDocumentEvent) {
		if (structuredDocumentEvent == null)
			return;
		IStructuredDocument structuredDocument = structuredDocumentEvent.getStructuredDocument();
		if (structuredDocument == null)
			return;
		// this should not happen, but for the case
		if (fStructuredDocument != null && fStructuredDocument != structuredDocument)
			setStructuredDocument(structuredDocument);

		internalSetNewDocument(structuredDocument);
	}

	private void internalSetNewDocument(IStructuredDocument structuredDocument) {
		if (structuredDocument == null)
			return;
		IStructuredDocumentRegionList flatNodes = structuredDocument.getRegionList();
		if ((flatNodes == null) || (flatNodes.getLength() == 0)) {
			return;
		}
		if (this.document == null)
			return; // being constructed
		XMLModelUpdater updater = getActiveUpdater();
		if (updater != null) { // being updated
			try {
				updater.replaceStructuredDocumentRegions(flatNodes, null);
			}
			catch (Exception ex) {
				Logger.logException(ex);
				this.refresh = true;
				handleRefresh();
			}
			finally {
				setActive(null);
			}
			// // for new model, we might need to
			// // re-init, e.g. if someone calls setText
			// // on an existing model
			// checkForReinit();
			return;
		}
		XMLModelNotifier notifier = getModelNotifier();
		boolean isChanging = notifier.isChanging();
		// call even if changing to notify doing new model
		getModelNotifier().beginChanging(true);
		XMLModelParser parser = getModelParser();
		setActive(parser);
		this.document.removeChildNodes();
		try {
			parser.replaceStructuredDocumentRegions(flatNodes, null);
		}
		catch (Exception ex) {
			Logger.logException(ex);
			// meaningless to refresh, because the result might be the same
		}
		finally {
			setActive(null);
			if (!isChanging) {
				getModelNotifier().endChanging();
			}
			// ignore refresh
			this.refresh = false;
		}
	}

	/**
	 */
	public void noChange(NoChangeEvent event) {
		XMLModelUpdater updater = getActiveUpdater();
		if (updater != null) { // being updated
			// cleanup updater staffs
			try {
				updater.replaceStructuredDocumentRegions(null, null);
			}
			catch (Exception ex) {
				Logger.logException(ex);
				this.refresh = true;
				handleRefresh();
			}
			finally {
				setActive(null);
			}
			// I guess no chanage means the model could not need re-init
			// checkForReinit();
			return;
		}
	}

	/**
	 * nodesReplaced method
	 * 
	 */
	public void nodesReplaced(StructuredDocumentRegionsReplacedEvent event) {
		if (event == null)
			return;
		IStructuredDocumentRegionList oldStructuredDocumentRegions = event.getOldStructuredDocumentRegions();
		IStructuredDocumentRegionList newStructuredDocumentRegions = event.getNewStructuredDocumentRegions();
		XMLModelUpdater updater = getActiveUpdater();
		if (updater != null) { // being updated
			try {
				updater.replaceStructuredDocumentRegions(newStructuredDocumentRegions, oldStructuredDocumentRegions);
			}
			catch (Exception ex) {
				if (ex.getClass().equals(StructuredDocumentRegionManagementException.class)) {
					Logger.traceException(TRACE_PARSER_MANAGEMENT_EXCEPTION, ex);
				}
				else {
					Logger.logException(ex);
				}
				this.refresh = true;
				handleRefresh();
			}
			finally {
				setActive(null);
			}
			// checkForReinit();
			return;
		}
		XMLModelNotifier notifier = getModelNotifier();
		boolean isChanging = notifier.isChanging();
		if (!isChanging)
			notifier.beginChanging();
		XMLModelParser parser = getModelParser();
		setActive(parser);
		try {
			parser.replaceStructuredDocumentRegions(newStructuredDocumentRegions, oldStructuredDocumentRegions);
		}
		catch (DOMException dex) {
			// this resulted from a text change, do not log it
			this.refresh = true;
			handleRefresh();
		}
		catch (Exception ex) {
			Logger.logException(ex);
			this.refresh = true;
			handleRefresh();
		}
		finally {
			setActive(null);
			if (!isChanging) {
				notifier.endChanging();
				handleRefresh();
			}
		}

	}

	/**
	 * regionChanged method
	 * 
	 * @param structuredDocumentEvent
	 */
	public void regionChanged(RegionChangedEvent event) {
		if (event == null)
			return;
		IStructuredDocumentRegion flatNode = event.getStructuredDocumentRegion();
		if (flatNode == null)
			return;
		ITextRegion region = event.getRegion();
		if (region == null)
			return;
		XMLModelUpdater updater = getActiveUpdater();
		if (updater != null) { // being updated
			try {
				updater.changeRegion(event, flatNode, region);
			}
			catch (Exception ex) {
				Logger.logException(ex);
				this.refresh = true;
				handleRefresh();
			}
			finally {
				setActive(null);
			}
			// checkForReinit();
			return;
		}
		XMLModelNotifier notifier = getModelNotifier();
		boolean isChanging = notifier.isChanging();
		if (!isChanging)
			notifier.beginChanging();
		XMLModelParser parser = getModelParser();
		setActive(parser);
		try {
			parser.changeRegion(event, flatNode, region);
		}
		catch (Exception ex) {
			Logger.logException(ex);
			this.refresh = true;
			handleRefresh();
		}
		finally {
			setActive(null);
			if (!isChanging) {
				notifier.endChanging();
				handleRefresh();
			}
		}
		// checkForReinit();
	}

	/**
	 * regionsReplaced method
	 * 
	 * @param event
	 */
	public void regionsReplaced(RegionsReplacedEvent event) {
		if (event == null)
			return;
		IStructuredDocumentRegion flatNode = event.getStructuredDocumentRegion();
		if (flatNode == null)
			return;
		ITextRegionList oldRegions = event.getOldRegions();
		ITextRegionList newRegions = event.getNewRegions();
		if (oldRegions == null && newRegions == null)
			return;
		XMLModelUpdater updater = getActiveUpdater();
		if (updater != null) { // being updated
			try {
				updater.replaceRegions(flatNode, newRegions, oldRegions);
			}
			catch (Exception ex) {
				Logger.logException(ex);
				this.refresh = true;
				handleRefresh();
			}
			finally {
				setActive(null);
			}
			// checkForReinit();
			return;
		}
		XMLModelNotifier notifier = getModelNotifier();
		boolean isChanging = notifier.isChanging();
		if (!isChanging)
			notifier.beginChanging();
		XMLModelParser parser = getModelParser();
		setActive(parser);
		try {
			parser.replaceRegions(flatNode, newRegions, oldRegions);
		}
		catch (Exception ex) {
			Logger.logException(ex);
			this.refresh = true;
			handleRefresh();
		}
		finally {
			setActive(null);
			if (!isChanging) {
				notifier.endChanging();
				handleRefresh();
			}
		}
		// checkForReinit();
	}

	/**
	 */
	public void releaseFromEdit() {
		if (!isShared()) {
			// this.document.releaseStyleSheets();
			// this.document.releaseDocumentType();
		}
		super.releaseFromEdit();
	}

	/**
	 */
	public void releaseFromRead() {
		if (!isShared()) {
			// this.document.releaseStyleSheets();
			// this.document.releaseDocumentType();
		}
		super.releaseFromRead();
	}

	/**
	 */
	private void setActive(Object active) {
		this.active = active;
		// side effect
		// when ever becomes active, besure tagNameCache is cleared
		// (and not used)
		if (active == null) {
			document.activateTagNameCache(true);
		}
		else {
			document.activateTagNameCache(false);
		}

	}

	/**
	 */
	public void setGenerator(ISourceGenerator generator) {
		this.generator = generator;
	}

	/**
	 */
	public void setModelNotifier(XMLModelNotifier notifier) {
		this.notifier = notifier;
	}

	/**
	 */
	public void setModelParser(XMLModelParser parser) {
		this.parser = parser;
	}

	/**
	 */
	public void setModelUpdater(XMLModelUpdater updater) {
		this.updater = updater;
	}

	/**
	 * setStructuredDocument method
	 * 
	 * @param structuredDocument
	 */
	public void setStructuredDocument(IStructuredDocument structuredDocument) {
		IStructuredDocument oldStructuredDocument = super.getStructuredDocument();
		if (structuredDocument == oldStructuredDocument)
			return; // nothing to do
		if (oldStructuredDocument != null)
			oldStructuredDocument.removeDocumentChangingListener(this);
		super.setStructuredDocument(structuredDocument);
		if (structuredDocument != null) {
			internalSetNewDocument(structuredDocument);
			structuredDocument.addDocumentChangingListener(this);
		}
	}

	/**
	 */
	protected void startTagChanged(Element element) {
		if (element == null)
			return;
		if (getActiveParser() == null) {
			XMLModelUpdater updater = getModelUpdater();
			setActive(updater);
			updater.initialize();
			updater.changeStartTag(element);
			setActive(null);
		}
		getModelNotifier().startTagChanged(element);
	}

	/**
	 * valueChanged method
	 * 
	 * @param node
	 *            org.w3c.dom.Node
	 */
	protected void valueChanged(Node node) {
		if (node == null)
			return;
		if (getActiveParser() == null) {
			XMLModelUpdater updater = getModelUpdater();
			setActive(updater);
			updater.initialize();
			updater.changeValue(node);
			setActive(null);
		}
		getModelNotifier().valueChanged(node);
	}

	/**
	 * NOT IMPLEMENTED. Is defined here in preparation of DOM 3.
	 */
	public Object getFeature(String feature, String version) {
		throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Not implemented in this version."); //$NON-NLS-1$
	}
}
