/*******************************************************************************
 * Copyright (c) 2001, 2013 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
 *     David Carver (STAR) - bug 296999 - Inefficient use of new String()
 *******************************************************************************/
package org.eclipse.wst.xml.core.internal.document;



import java.util.Enumeration;
import java.util.Iterator;

import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent;
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.contentmodel.CMElementDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQuery;
import org.eclipse.wst.xml.core.internal.modelquery.ModelQueryUtil;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.eclipse.wst.xml.core.internal.provisional.document.ISourceGenerator;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Text;


/**
 * XMLModelUpdater class
 */
public class XMLModelUpdater {
	private int diff = 0;
	private int gapLength = 0;
	private int gapOffset = 0;
	private IStructuredDocumentRegion gapStructuredDocumentRegion = null;
	private ISourceGenerator generator = null;
	private DOMModelImpl model = null;
	private NodeImpl nextNode = null;
	private NodeImpl parentNode = null;

	protected XMLModelUpdater(DOMModelImpl model) {
		super();

		if (model != null) {
			this.model = model;
			this.generator = model.getGenerator();
		}
	}

	/**
	 * changeAttrValue method
	 * 
	 * @param attrNode
	 *            org.w3c.dom.Attr
	 */
	private void changeAttrName(Attr attrNode) {
		if (attrNode == null)
			return;

		AttrImpl attr = (AttrImpl) attrNode;
		ElementImpl element = (ElementImpl) attr.getOwnerElement();
		if (element == null)
			return;

		if (element.isCommentTag()) {
			changeStartTag(element);
			return;
		}

		int offset = element.getStartOffset();
		int start = offset;
		int end = offset;

		String name = attr.getName();
		if (name == null)
			name = NodeImpl.EMPTY_STRING;
		ITextRegion nameRegion = attr.getNameRegion();
		if (nameRegion == null)
			return; // error
		start += nameRegion.getStart();
		// use getTextEnd() because getEnd() may include the tailing spaces
		end += nameRegion.getTextEnd();

		replaceSource(name, start, end);
	}

	/**
	 * changeAttrValue method
	 * 
	 * @param attrNode
	 *            org.w3c.dom.Attr
	 */
	private void changeAttrValue(Attr attrNode) {
		if (attrNode == null)
			return;

		AttrImpl attr = (AttrImpl) attrNode;
		ElementImpl element = (ElementImpl) attr.getOwnerElement();
		if (element == null)
			return;

		if (element.isCommentTag()) {
			changeStartTag(element);
			return;
		}

		int offset = element.getStartOffset();
		int start = offset;
		int end = offset;

		String value = null;
		ITextRegion valueRegion = attr.getValueRegion();
		if (valueRegion != null) {
			char quote = 0; // no quote preference
			// DW: 4/16/2003 due to change in structuredDocument ... we need a
			// flatnode to
			// get at region values. For now I'll assume this is always the
			// first
			// flatnode .. may need to make smarter later (e.g. to search for
			// the flatnode that this.valueRegion belongs to.
			IStructuredDocumentRegion documentRegion = element.getFirstStructuredDocumentRegion();
			String oldValue = documentRegion.getText(valueRegion);
			if (oldValue != null && oldValue.length() > 0) {
				char firstChar = oldValue.charAt(0);
				if (firstChar == '"' || firstChar == '\'') {
					quote = firstChar;
				}
			}

			ITextRegion startRegion = valueRegion;

			value = this.generator.generateAttrValue(attr, quote);
			if (value == null) {
				value = NodeImpl.EMPTY_STRING;
				// remove equal too
				ITextRegion equalRegion = attr.getEqualRegion();
				if (equalRegion != null)
					startRegion = equalRegion;
			}
			attr.setValueRegion(valueRegion); // reset value

			start += startRegion.getStart();
			// use getTextEnd() because getEnd() may include the tailing
			// spaces
			end += valueRegion.getTextEnd();
		}
		else {
			ITextRegion equalRegion = attr.getEqualRegion();

			value = this.generator.generateAttrValue(attr);
			if (value == null) {
				if (equalRegion == null)
					return; // nothng to do
				value = NodeImpl.EMPTY_STRING;
				// remove equal
				start += equalRegion.getStart();
				end += equalRegion.getTextEnd();
			}
			else {
				if (equalRegion != null) {
					// use getTextEnd() because getEnd() may include the
					// tailing spaces
					start += equalRegion.getTextEnd();
				}
				else {
					ITextRegion nameRegion = attr.getNameRegion();
					if (nameRegion == null)
						return; // must never happen
					// use getTextEnd() because getEnd() may include the
					// tailing spaces
					start += nameRegion.getTextEnd();
					value = '=' + value;
				}
				end = start;
			}
		}

		replaceSource(value, start, end);
	}

	/**
	 */
	void changeEndTag(Element element) {
		String source = this.generator.generateEndTag(element);
		if (source == null)
			return;
		int length = source.length();
		if (length == 0)
			return;

		ElementImpl impl = (ElementImpl) element;
		int offset = impl.getEndStartOffset();
		int start = offset;
		int end = offset;
		if (impl.hasEndTag()) {
			end = impl.getEndOffset();
			this.gapStructuredDocumentRegion = impl.getEndStructuredDocumentRegion();
			impl.setEndStructuredDocumentRegion(new StructuredDocumentRegionProxy(offset, length));
		}

		replaceSource(source, start, end);
	}

	/**
	 * changeName method
	 * 
	 * @param node
	 *            org.w3c.dom.Node
	 */
	void changeName(Node node) {
		if (node == null)
			return;
		if (getStructuredDocument() == null)
			return;

		// support changing name of attribute for setPrefix()
		short nodeType = node.getNodeType();
		if (nodeType == Node.ATTRIBUTE_NODE) {
			changeAttrName((Attr) node);
			return;
		}

		// not supported
		return;
	}

	void changeRegion(RegionChangedEvent change, IStructuredDocumentRegion flatNode, ITextRegion region) {
		if (change.getOffset() >= flatNode.getStartOffset() + region.getTextEnd()) {
			// change is entirely in white-space
			return;
		}

		NodeImpl root = (NodeImpl) this.model.getDocument();
		this.parentNode = root;
		this.nextNode = (NodeImpl) root.getFirstChild();

		removeGapStructuredDocumentRegion(flatNode);
		insertGapStructuredDocumentRegionBefore(flatNode.getStart());
		changeStructuredDocumentRegion(flatNode);
		insertGapStructuredDocumentRegionAfter(flatNode.getEnd());
	}

	/**
	 * This is a fallback method to regenerate the start tag.
	 */
	void changeStartTag(Element element) {
		if (element == null)
			return;
		ElementImpl impl = (ElementImpl) element;

		if (!impl.hasStartTag() && !impl.hasEndTag()) {
			// need to generate the start and the end tags
			Node parent = element.getParentNode();
			if (parent != null) {
				replaceChild(parent, element, element);
				return;
			}
			// else error
		}

		String source = this.generator.generateStartTag(element);
		if (source == null)
			return;
		int length = source.length();
		if (length == 0)
			return;

		int offset = impl.getStartOffset();
		int start = offset;
		int end = offset;
		if (impl.hasStartTag()) {
			end = impl.getStartEndOffset();
			this.gapStructuredDocumentRegion = impl.getStartStructuredDocumentRegion();
		}
		impl.setStartStructuredDocumentRegion(new StructuredDocumentRegionProxy(offset, length));

		replaceSource(source, start, end);
	}

	private void changeStructuredDocumentRegion(IStructuredDocumentRegion oldStructuredDocumentRegion) {
		if (oldStructuredDocumentRegion == null)
			return; // error
		if (this.parentNode == null)
			return; // error

		int oldOffset = oldStructuredDocumentRegion.getStart();
		int oldEnd = oldStructuredDocumentRegion.getEnd();
		boolean isEndTag = false;

		// find owner node
		NodeImpl ownerNode = null;
		while (this.parentNode != null) {
			if (this.nextNode != null) {
				IStructuredDocumentRegion nextStructuredDocumentRegion = this.nextNode.getStructuredDocumentRegion();
				if (nextStructuredDocumentRegion != null) {
					if (nextStructuredDocumentRegion == oldStructuredDocumentRegion) {
						ownerNode = this.nextNode;
						break;
					}
					int nextOffset = nextStructuredDocumentRegion.getStart();
					if (nextOffset == oldOffset) { // found
						ownerNode = this.nextNode;
						break;
					}
					if (this.nextNode.getNodeType() == Node.TEXT_NODE) {
						TextImpl text = (TextImpl) this.nextNode;
						if (text.hasStructuredDocumentRegion(oldStructuredDocumentRegion)) {
							ownerNode = this.nextNode;
							break;
						}
						int nextEnd = nextStructuredDocumentRegion.getEnd();
						if (nextOffset < oldEnd && nextEnd > oldOffset) {
							ownerNode = this.nextNode;
							break;
						}
					}
				}

				Node child = this.nextNode.getFirstChild();
				if (child != null) {
					this.parentNode = this.nextNode;
					this.nextNode = (NodeImpl) child;
					continue;
				}

				if (this.nextNode.getNodeType() == Node.ELEMENT_NODE) {
					this.parentNode = this.nextNode;
					this.nextNode = null;
					continue;
				}

				this.nextNode = (NodeImpl) this.nextNode.getNextSibling();
				if (this.nextNode != null)
					continue;
			}

			if (this.parentNode.getNodeType() == Node.ELEMENT_NODE) {
				ElementImpl element = (ElementImpl) this.parentNode;
				IStructuredDocumentRegion endStructuredDocumentRegion = element.getEndStructuredDocumentRegion();
				if (endStructuredDocumentRegion != null) {
					if (endStructuredDocumentRegion == oldStructuredDocumentRegion) {
						ownerNode = this.parentNode;
						isEndTag = true;
						break;
					}
					int endOffset = endStructuredDocumentRegion.getStart();
					if (endOffset == oldOffset) { // found
						ownerNode = this.parentNode;
						isEndTag = true;
						break;
					}
				}
			}

			this.nextNode = (NodeImpl) this.parentNode.getNextSibling();
			this.parentNode = (NodeImpl) this.parentNode.getParentNode();
		}
		if (ownerNode == null)
			throw new StructuredDocumentRegionManagementException();

		short nodeType = ownerNode.getNodeType();
		if (nodeType == Node.ELEMENT_NODE) {
			ElementImpl element = (ElementImpl) ownerNode;
			if (isEndTag) {
				element.setEndStructuredDocumentRegion(oldStructuredDocumentRegion);
			}
			else {
				element.setStartStructuredDocumentRegion(oldStructuredDocumentRegion);
				updateAttrRegions(element, oldStructuredDocumentRegion);
			}
		}
		else if (nodeType == Node.TEXT_NODE) {
			TextImpl text = (TextImpl) ownerNode;

			IStructuredDocumentRegion flatNode = text.getStructuredDocumentRegion();
			if (flatNode == oldStructuredDocumentRegion) {
				int newOffset = oldOffset;
				int newEnd = oldEnd;
				if (oldOffset == this.gapOffset) {
					newOffset += this.diff;
				}
				else {
					newEnd = this.gapOffset;
				}
				int newLength = newEnd - newOffset;
				IStructuredDocumentRegion newStructuredDocumentRegion = new StructuredDocumentRegionProxy(newOffset, newLength, oldStructuredDocumentRegion);
				text.setStructuredDocumentRegion(newStructuredDocumentRegion);

				if (oldEnd > newEnd) {
					this.nextNode = (NodeImpl) text.getNextSibling();
					changeStructuredDocumentRegion(oldStructuredDocumentRegion);
				}
				return;
			}

			if (flatNode instanceof StructuredDocumentRegionProxy) {
				StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) flatNode;
				int offset = proxy.getOffset();
				int end = offset + proxy.getLength();
				if (proxy.getStructuredDocumentRegion() == null) {
					if (offset == oldOffset && end == oldEnd) {
						text.setStructuredDocumentRegion(oldStructuredDocumentRegion);
					}
					else {
						if (end > oldEnd) {
							StructuredDocumentRegionContainer container = new StructuredDocumentRegionContainer();
							container.appendStructuredDocumentRegion(oldStructuredDocumentRegion);
							proxy.setOffset(oldEnd);
							proxy.setLength(end - oldEnd);
							container.appendStructuredDocumentRegion(proxy);
							text.setStructuredDocumentRegion(container);
						}
						else {
							proxy.setStructuredDocumentRegion(oldStructuredDocumentRegion);

							if (end < oldEnd) { // to be shared
								this.nextNode = (NodeImpl) text.getNextSibling();
								changeStructuredDocumentRegion(oldStructuredDocumentRegion);
							}
						}
					}
					return;
				}

				if (offset >= this.gapOffset) {
					proxy.setOffset(offset + this.diff);
					end += this.diff;
				}
				if (end < oldEnd) { // to be shared
					this.nextNode = (NodeImpl) text.getNextSibling();
					changeStructuredDocumentRegion(oldStructuredDocumentRegion);
					return;
				}
			}
			else if (flatNode instanceof StructuredDocumentRegionContainer) {
				StructuredDocumentRegionContainer container = (StructuredDocumentRegionContainer) flatNode;
				int count = container.getStructuredDocumentRegionCount();
				for (int i = 0; i < count; i++) {
					IStructuredDocumentRegion content = container.getStructuredDocumentRegion(i);
					if (content == null)
						continue; // error
					if (content == oldStructuredDocumentRegion) {
						int newOffset = oldOffset;
						int newEnd = oldEnd;
						if (oldOffset == this.gapOffset) {
							newOffset += this.diff;
						}
						else {
							newEnd = this.gapOffset;
						}
						int newLength = newEnd - newOffset;
						IStructuredDocumentRegion newStructuredDocumentRegion = new StructuredDocumentRegionProxy(newOffset, newLength, oldStructuredDocumentRegion);
						container.replaceStructuredDocumentRegion(newStructuredDocumentRegion, i);

						if (oldEnd > newEnd) { // to be shared
							this.nextNode = (NodeImpl) text.getNextSibling();
							changeStructuredDocumentRegion(oldStructuredDocumentRegion);
						}
						return;
					}

					if (content instanceof StructuredDocumentRegionProxy) {
						StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) content;
						int offset = proxy.getOffset();
						int end = offset + proxy.getLength();
						if (end <= oldOffset)
							continue;
						if (proxy.getStructuredDocumentRegion() == null) {
							if (offset == oldOffset && end == oldEnd) {
								container.replaceStructuredDocumentRegion(oldStructuredDocumentRegion, i);
							}
							else {
								if (end > oldEnd) {
									container.insertStructuredDocumentRegion(oldStructuredDocumentRegion, i);
									proxy.setOffset(oldEnd);
									proxy.setLength(end - oldEnd);
								}
								else {
									proxy.setStructuredDocumentRegion(oldStructuredDocumentRegion);

									if (end < oldEnd) { // to be shared
										this.nextNode = (NodeImpl) text.getNextSibling();
										changeStructuredDocumentRegion(oldStructuredDocumentRegion);
									}
								}
							}
							return;
						}

						if (offset >= this.gapOffset) {
							proxy.setOffset(offset + this.diff);
							end += this.diff;
						}
						if (end < oldEnd) { // to be shared
							this.nextNode = (NodeImpl) text.getNextSibling();
							changeStructuredDocumentRegion(oldStructuredDocumentRegion);
							return;
						}
					}
				}
			}
			else {
				throw new StructuredDocumentRegionManagementException();
			}
		}
		else {
			ownerNode.setStructuredDocumentRegion(oldStructuredDocumentRegion);
		}
	}

	/**
	 */
	private void changeTextData(Text text) {
		if (text == null)
			return;

		String source = this.generator.generateSource(text);
		if (source == null)
			source = NodeImpl.EMPTY_STRING;
		int length = source.length();

		TextImpl impl = (TextImpl) text;
		int start = impl.getStartOffset();
		int end = impl.getEndOffset();
		int offset = start;

		// make sure previous tag is closed
		Node prev = text.getPreviousSibling();
		if (prev != null) {
			String preTag = getCloseTag((IDOMNode) prev);
			if (preTag != null && preTag.length() > 0) {
				offset += preTag.length();
				source = preTag + source;
			}
		}
		else {
			Node parent = text.getParentNode();
			if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) {
				ElementImpl element = (ElementImpl) parent;
				String preTag = getStartCloseTag(element);
				if (preTag != null && preTag.length() > 0) {
					offset += preTag.length();
					StringBuffer buffer = new StringBuffer();
					buffer.append(preTag);
					buffer.append(source);
					if (text.getNextSibling() == null && !element.hasEndTag() && (element.isJSPContainer() || element.isCDATAContainer())) {
						// need to generate the end tag
						String postTag = this.generator.generateEndTag(element);
						if (postTag != null) {
							int postLength = postTag.length();
							if (postLength > 0) {
								buffer.append(postTag);
								int postOffset = offset + length;
								IStructuredDocumentRegion flatNode = new StructuredDocumentRegionProxy(postOffset, postLength);
								element.setEndStructuredDocumentRegion(flatNode);
							}
						}
					}
					source = buffer.toString();
				}
			}
		}

		this.gapStructuredDocumentRegion = impl.getStructuredDocumentRegion();
		IStructuredDocumentRegion newStructuredDocumentRegion = null;
		if (length > 0)
			newStructuredDocumentRegion = new StructuredDocumentRegionProxy(offset, length);
		impl.setStructuredDocumentRegion(newStructuredDocumentRegion);

		replaceSource(source, start, end);
	}

	/**
	 * changeValue method
	 * 
	 * @param node
	 *            org.w3c.dom.Node
	 */
	void changeValue(Node node) {
		if (node == null)
			return;
		if (getStructuredDocument() == null)
			return;

		short nodeType = node.getNodeType();
		if (nodeType == Node.TEXT_NODE) {
			changeTextData((Text) node);
			return;
		}
		if (nodeType == Node.ATTRIBUTE_NODE) {
			changeAttrValue((Attr) node);
			return;
		}
		if (nodeType == Node.ELEMENT_NODE) {
			changeStartTag((Element) node);
			return;
		}

		String source = this.generator.generateSource(node);
		if (source == null)
			source = NodeImpl.EMPTY_STRING;
		int length = source.length();

		NodeImpl impl = (NodeImpl) node;
		int start = impl.getStartOffset();
		int end = impl.getEndOffset();

		this.gapStructuredDocumentRegion = impl.getStructuredDocumentRegion();
		IStructuredDocumentRegion flatNode = null;
		if (length > 0)
			flatNode = new StructuredDocumentRegionProxy(start, length);
		impl.setStructuredDocumentRegion(flatNode);

		replaceSource(source, start, end);
	}

	/**
	 */
	private String getAttrValueClose(IDOMElement element) {
		if (element == null)
			return null;

		IStructuredDocumentRegion flatNode = element.getStartStructuredDocumentRegion();
		if (flatNode == null)
			return null;
		ITextRegion region = StructuredDocumentRegionUtil.getLastRegion(flatNode);
		if (region == null || region.getType() != DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE)
			return null;
		String value = flatNode.getText(region);
		if (value == null)
			return null;
		int length = value.length();
		if (length == 0)
			return null;

		// check open JSP tag
		boolean closeJSPTag = false;
		int offset = value.indexOf(JSPTag.TAG_OPEN);
		while (offset >= 0) {
			offset = value.indexOf(JSPTag.TAG_CLOSE, offset + 2);
			if (offset < 0) {
				closeJSPTag = true;
				break;
			}
			offset = value.indexOf(JSPTag.TAG_OPEN, offset + 2);
		}

		// check quote
		boolean closeQuote = false;
		char firstChar = value.charAt(0);
		if (firstChar == '"' || firstChar == '\'') {
			if (closeJSPTag || length == 1 || value.charAt(length - 1) != firstChar) {
				closeQuote = true;
			}
		}

		if (!closeJSPTag && !closeQuote)
			return null;

		StringBuffer buffer = new StringBuffer();
		if (closeJSPTag)
			buffer.append(JSPTag.TAG_CLOSE);
		if (closeQuote)
			buffer.append(firstChar);
		return buffer.toString();
	}

	/**
	 * Gather close tags recursively.
	 */
	private String getCloseTag(IDOMNode node) {
		if (node == null || node.isClosed())
			return null;

		if (node.getNodeType() != Node.ELEMENT_NODE) {
			return this.generator.generateCloseTag(node);
		}

		ElementImpl element = (ElementImpl) node;
		if (element.hasEndTag()) {
			// end tag is not closed
			return this.generator.generateCloseTag(element);
		}

		// no end tag
		int offset = element.getEndOffset();
		StringBuffer buffer = new StringBuffer();

		IDOMNode lastChild = (IDOMNode) element.getLastChild();
		if (lastChild == null) {
			if (!element.isStartTagClosed()) {
				if (element.preferEmptyTag())
					element.setEmptyTag(true);
				String closeTag = getStartCloseTag(element);
				if (closeTag != null) {
					int length = closeTag.length();
					if (length > 0) {
						buffer.append(closeTag);
						offset += length;
					}
				}
			}
		}
		else {
			String closeTag = getCloseTag(lastChild);
			if (closeTag != null) {
				int length = closeTag.length();
				if (length > 0) {
					buffer.append(closeTag);
					offset += length;
				}
			}
		}

		String endTag = this.generator.generateEndTag(element);
		if (endTag != null) {
			int length = endTag.length();
			if (length > 0) {
				buffer.append(endTag);
				IStructuredDocumentRegion flatNode = new StructuredDocumentRegionProxy(offset, length);
				element.setEndStructuredDocumentRegion(flatNode);
			}
		}

		return buffer.toString();
	}

	private boolean isSelfClosedContainer(IDOMElement element) {
		if (element.getStartStructuredDocumentRegion() != null) {
			final ModelQuery mq = ModelQueryUtil.getModelQuery(element.getOwnerDocument());
			if (mq !=  null) {
				final CMElementDeclaration node = mq.getCMElementDeclaration(element);
				if (node != null) {
					return node.getContentType() != CMElementDeclaration.EMPTY && element.getStartStructuredDocumentRegion().getLastRegion().getType() == DOMRegionContext.XML_EMPTY_TAG_CLOSE;
				}
			}
		}
		return false;
	}
	/**
	 */
	private String getStartCloseTag(IDOMElement element) {
		if (element == null || element.isStartTagClosed())
			return null;

		StringBuffer buffer = new StringBuffer();
		String attrValueClose = getAttrValueClose(element);
		if (attrValueClose != null)
			buffer.append(attrValueClose);
		String closeTag = this.generator.generateCloseTag(element);
		if (closeTag != null)
			buffer.append(closeTag);
		return buffer.toString();
	}

	private IStructuredDocument getStructuredDocument() {
		if (model == null)
			return null;
		return model.getStructuredDocument();
	}

	/**
	 */
	void initialize() {
		this.gapStructuredDocumentRegion = null;
		this.gapOffset = 0;
		this.gapLength = 0;
		this.diff = 0;
		this.parentNode = null;
		this.nextNode = null;
	}

	private void insertGapStructuredDocumentRegionAfter(int endOffset) {
		if (this.gapStructuredDocumentRegion == null)
			return;

		if (this.gapStructuredDocumentRegion instanceof StructuredDocumentRegionProxy) {
			StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) this.gapStructuredDocumentRegion;
			IStructuredDocumentRegion flatNode = proxy.getStructuredDocumentRegion();
			if (flatNode != null)
				insertStructuredDocumentRegion(flatNode);
		}
		else if (this.gapStructuredDocumentRegion instanceof StructuredDocumentRegionContainer) {
			StructuredDocumentRegionContainer container = (StructuredDocumentRegionContainer) this.gapStructuredDocumentRegion;
			int count = container.getStructuredDocumentRegionCount();
			for (int i = 0; i < count; i++) {
				IStructuredDocumentRegion content = container.getStructuredDocumentRegion(i);
				if (content == null)
					continue;
				if (content.getStart() < endOffset)
					continue;
				if (content instanceof StructuredDocumentRegionProxy) {
					StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) content;
					IStructuredDocumentRegion flatNode = proxy.getStructuredDocumentRegion();
					if (flatNode != null)
						insertStructuredDocumentRegion(flatNode);
				}
				else {
					insertStructuredDocumentRegion(content);
				}
			}
		}
		else {
			insertStructuredDocumentRegion(this.gapStructuredDocumentRegion);
		}
	}

	private void insertGapStructuredDocumentRegionBefore(int startOffset) {
		if (this.gapStructuredDocumentRegion == null)
			return;

		if (this.gapStructuredDocumentRegion instanceof StructuredDocumentRegionProxy) {
			StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) this.gapStructuredDocumentRegion;
			IStructuredDocumentRegion flatNode = proxy.getStructuredDocumentRegion();
			if (flatNode != null)
				insertStructuredDocumentRegion(flatNode);
		}
		else if (this.gapStructuredDocumentRegion instanceof StructuredDocumentRegionContainer) {
			StructuredDocumentRegionContainer container = (StructuredDocumentRegionContainer) this.gapStructuredDocumentRegion;
			int count = container.getStructuredDocumentRegionCount();
			for (int i = 0; i < count; i++) {
				IStructuredDocumentRegion content = container.getStructuredDocumentRegion(i);
				if (content == null)
					continue;
				if (content.getStart() >= startOffset)
					return;
				if (content instanceof StructuredDocumentRegionProxy) {
					StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) content;
					IStructuredDocumentRegion flatNode = proxy.getStructuredDocumentRegion();
					if (flatNode != null)
						insertStructuredDocumentRegion(flatNode);
				}
				else {
					insertStructuredDocumentRegion(content);
				}
			}
		}
		else {
			insertStructuredDocumentRegion(this.gapStructuredDocumentRegion);
		}
	}

	/**
	 */
	private void insertStructuredDocumentRegion(IStructuredDocumentRegion newStructuredDocumentRegion) {
		if (newStructuredDocumentRegion == null)
			return; // error
		if (this.parentNode == null)
			return; // error

		int newOffset = newStructuredDocumentRegion.getStart();
		int newEnd = newStructuredDocumentRegion.getEnd();
		boolean isEndTag = false;

		// find owner node
		NodeImpl ownerNode = null;
		while (this.parentNode != null) {
			if (this.nextNode != null) {
				IStructuredDocumentRegion nextStructuredDocumentRegion = this.nextNode.getStructuredDocumentRegion();
				if (nextStructuredDocumentRegion != null) {
					int nextOffset = nextStructuredDocumentRegion.getStart();
					if (nextOffset == newOffset) { // found
						ownerNode = this.nextNode;
						break;
					}
					if (this.nextNode.getNodeType() == Node.TEXT_NODE) {
						int nextEnd = nextStructuredDocumentRegion.getEnd();
						if (nextOffset < newEnd && nextEnd > newOffset) {
							ownerNode = this.nextNode;
							break;
						}
					}
				}

				Node child = this.nextNode.getFirstChild();
				if (child != null) {
					this.parentNode = this.nextNode;
					this.nextNode = (NodeImpl) child;
					continue;
				}

				if (this.nextNode.getNodeType() == Node.ELEMENT_NODE) {
					this.parentNode = this.nextNode;
					this.nextNode = null;
					continue;
				}

				this.nextNode = (NodeImpl) this.nextNode.getNextSibling();
				if (this.nextNode != null)
					continue;
			}

			if (this.parentNode.getNodeType() == Node.ELEMENT_NODE) {
				ElementImpl element = (ElementImpl) this.parentNode;
				IStructuredDocumentRegion endStructuredDocumentRegion = element.getEndStructuredDocumentRegion();
				if (endStructuredDocumentRegion != null) {
					int endOffset = endStructuredDocumentRegion.getStart();
					if (endOffset == newOffset) { // found
						ownerNode = this.parentNode;
						isEndTag = true;
						break;
					}
				}
			}

			this.nextNode = (NodeImpl) this.parentNode.getNextSibling();
			this.parentNode = (NodeImpl) this.parentNode.getParentNode();
		}
		if (ownerNode == null)
			throw new StructuredDocumentRegionManagementException();

		short nodeType = ownerNode.getNodeType();
		if (nodeType == Node.ELEMENT_NODE) {
			ElementImpl element = (ElementImpl) ownerNode;
			if (isEndTag) {
				element.setEndStructuredDocumentRegion(newStructuredDocumentRegion);
			}
			else {
				element.setStartStructuredDocumentRegion(newStructuredDocumentRegion);
				updateAttrRegions(element, newStructuredDocumentRegion);
			}
		}
		else if (nodeType == Node.TEXT_NODE) {
			TextImpl text = (TextImpl) ownerNode;
			IStructuredDocumentRegion oldStructuredDocumentRegion = text.getStructuredDocumentRegion();
			if (oldStructuredDocumentRegion == null) {
				throw new StructuredDocumentRegionManagementException();
			}
			int oldOffset = oldStructuredDocumentRegion.getStart();
			int oldEnd = oldStructuredDocumentRegion.getEnd();
			if (oldOffset == newOffset && oldEnd == newEnd) {
				text.setStructuredDocumentRegion(newStructuredDocumentRegion);
				return;
			}

			if (oldStructuredDocumentRegion instanceof StructuredDocumentRegionProxy) {
				StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) oldStructuredDocumentRegion;
				if (oldEnd > newEnd) {
					StructuredDocumentRegionContainer container = new StructuredDocumentRegionContainer();
					if (oldOffset == newOffset) {
						container.appendStructuredDocumentRegion(newStructuredDocumentRegion);
					}
					else {
						StructuredDocumentRegionProxy newProxy = new StructuredDocumentRegionProxy();
						newProxy.setOffset(oldOffset);
						newProxy.setLength(newEnd - oldOffset);
						newProxy.setStructuredDocumentRegion(newStructuredDocumentRegion);
						container.appendStructuredDocumentRegion(newProxy);
					}
					proxy.setOffset(newEnd);
					proxy.setLength(oldEnd - newEnd);
					container.appendStructuredDocumentRegion(proxy);
					text.setStructuredDocumentRegion(container);
				}
				else {
					proxy.setStructuredDocumentRegion(newStructuredDocumentRegion);

					if (oldEnd < newEnd) { // to be shared
						this.nextNode = (NodeImpl) text.getNextSibling();
						insertStructuredDocumentRegion(newStructuredDocumentRegion);
					}
				}
				return;
			}

			if (oldStructuredDocumentRegion instanceof StructuredDocumentRegionContainer) {
				StructuredDocumentRegionContainer container = (StructuredDocumentRegionContainer) oldStructuredDocumentRegion;
				int count = container.getStructuredDocumentRegionCount();
				for (int i = 0; i < count; i++) {
					IStructuredDocumentRegion content = container.getStructuredDocumentRegion(i);
					if (content == null)
						continue; // error
					int offset = content.getStart();
					int end = content.getEnd();
					if (end <= newOffset)
						continue;
					if (offset == newOffset && end == newEnd) {
						container.replaceStructuredDocumentRegion(newStructuredDocumentRegion, i);
						return;
					}

					if (content instanceof StructuredDocumentRegionProxy) {
						StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) content;
						if (end > newEnd) {
							if (offset == newOffset) {
								container.insertStructuredDocumentRegion(newStructuredDocumentRegion, i);
							}
							else {
								StructuredDocumentRegionProxy newProxy = new StructuredDocumentRegionProxy();
								newProxy.setOffset(offset);
								newProxy.setLength(newEnd - offset);
								newProxy.setStructuredDocumentRegion(newStructuredDocumentRegion);
								container.insertStructuredDocumentRegion(newProxy, i);
							}
							proxy.setOffset(newEnd);
							proxy.setLength(end - newEnd);
							return;
						}
						else {
							proxy.setStructuredDocumentRegion(newStructuredDocumentRegion);
							if (end == newEnd)
								return;
						}
					}
				}

				if (oldEnd < newEnd) { // to be shared
					this.nextNode = (NodeImpl) text.getNextSibling();
					insertStructuredDocumentRegion(newStructuredDocumentRegion);
				}
				return;
			}
			else {
				throw new StructuredDocumentRegionManagementException();
			}
		}
		else {
			ownerNode.setStructuredDocumentRegion(newStructuredDocumentRegion);
		}
	}

	private void removeGapStructuredDocumentRegion(IStructuredDocumentRegion oldStructuredDocumentRegion) {
		if (this.gapStructuredDocumentRegion == null)
			return;

		if (this.gapStructuredDocumentRegion == oldStructuredDocumentRegion) {
			this.gapStructuredDocumentRegion = null;
			return;
		}

		if (this.gapStructuredDocumentRegion instanceof StructuredDocumentRegionProxy) {
			StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) this.gapStructuredDocumentRegion;
			IStructuredDocumentRegion flatNode = proxy.getStructuredDocumentRegion();
			if (flatNode == oldStructuredDocumentRegion)
				this.gapStructuredDocumentRegion = null;
		}
		else if (this.gapStructuredDocumentRegion instanceof StructuredDocumentRegionContainer) {
			StructuredDocumentRegionContainer container = (StructuredDocumentRegionContainer) this.gapStructuredDocumentRegion;
			int count = container.getStructuredDocumentRegionCount();
			for (int i = 0; i < count; i++) {
				IStructuredDocumentRegion content = container.getStructuredDocumentRegion(i);
				if (content == null)
					continue;
				if (content == oldStructuredDocumentRegion) {
					if (count > 1)
						container.removeStructuredDocumentRegion(i);
					else
						this.gapStructuredDocumentRegion = null;
					return;
				}
				if (content instanceof StructuredDocumentRegionProxy) {
					StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) content;
					if (proxy.getStructuredDocumentRegion() == oldStructuredDocumentRegion) {
						if (count > 1)
							container.removeStructuredDocumentRegion(i);
						else
							this.gapStructuredDocumentRegion = null;
						return;
					}
				}
			}
		}
	}

	private void removeStructuredDocumentRegion(IStructuredDocumentRegion oldStructuredDocumentRegion) {
		if (oldStructuredDocumentRegion == null)
			return; // error
		if (this.parentNode == null)
			return; // error

		int gapEnd = this.gapOffset + this.gapLength;
		int oldOffset = oldStructuredDocumentRegion.getStart();
		int oldEnd = oldStructuredDocumentRegion.getEnd();
		if (oldOffset >= this.gapOffset && oldEnd <= gapEnd)
			return; // do nothing
		int oldLength = oldEnd - oldOffset;
		if (oldOffset >= gapEnd)
			oldOffset += this.diff;

		// find owner node
		NodeImpl ownerNode = null;
		ElementImpl ownerEndTag = null;
		TextImpl ownerText = null;
		while (this.parentNode != null) {
			if (this.nextNode != null) {
				if (this.nextNode.getStructuredDocumentRegion() == oldStructuredDocumentRegion) {
					ownerNode = this.nextNode;
					break;
				}
				if (this.nextNode.getNodeType() == Node.TEXT_NODE) {
					TextImpl text = (TextImpl) this.nextNode;
					if (text.hasStructuredDocumentRegion(oldStructuredDocumentRegion)) {
						ownerNode = this.nextNode;
						ownerText = text;
						break;
					}
				}

				Node child = this.nextNode.getFirstChild();
				if (child != null) {
					this.parentNode = this.nextNode;
					this.nextNode = (NodeImpl) child;
					continue;
				}

				if (this.nextNode.getNodeType() == Node.ELEMENT_NODE) {
					this.parentNode = this.nextNode;
					this.nextNode = null;
					continue;
				}

				this.nextNode = (NodeImpl) this.nextNode.getNextSibling();
				if (this.nextNode != null)
					continue;
			}

			if (this.parentNode.getNodeType() == Node.ELEMENT_NODE) {
				ElementImpl element = (ElementImpl) this.parentNode;
				if (element.getEndStructuredDocumentRegion() == oldStructuredDocumentRegion) {
					ownerNode = this.parentNode;
					ownerEndTag = element;
					break;
				}
			}

			this.nextNode = (NodeImpl) this.parentNode.getNextSibling();
			this.parentNode = (NodeImpl) this.parentNode.getParentNode();
		}
		if (ownerNode == null)
			throw new StructuredDocumentRegionManagementException();

		if (ownerText != null) {
			IStructuredDocumentRegion flatNode = ownerText.getStructuredDocumentRegion();
			if (flatNode == oldStructuredDocumentRegion) {
				IStructuredDocumentRegion newStructuredDocumentRegion = new StructuredDocumentRegionProxy(oldOffset, oldLength);
				ownerText.setStructuredDocumentRegion(newStructuredDocumentRegion);
				return;
			}

			if (flatNode instanceof StructuredDocumentRegionProxy) {
				StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) flatNode;
				if (proxy.getStructuredDocumentRegion() != oldStructuredDocumentRegion) {
					throw new StructuredDocumentRegionManagementException();
				}
				int offset = proxy.getOffset();
				int end = offset + proxy.getLength();
				if (offset >= this.gapOffset) {
					proxy.setOffset(offset + this.diff);
				}
				proxy.setStructuredDocumentRegion(null);
				if (end < oldEnd && (end < this.gapOffset || oldEnd > gapEnd)) { // has
					// shared
					removeStructuredDocumentRegion(oldStructuredDocumentRegion);
					return;
				}
			}
			else if (flatNode instanceof StructuredDocumentRegionContainer) {
				StructuredDocumentRegionContainer container = (StructuredDocumentRegionContainer) flatNode;
				int count = container.getStructuredDocumentRegionCount();
				for (int i = 0; i < count; i++) {
					IStructuredDocumentRegion content = container.getStructuredDocumentRegion(i);
					if (content == null)
						continue; // error
					if (content == oldStructuredDocumentRegion) {
						IStructuredDocumentRegion newStructuredDocumentRegion = new StructuredDocumentRegionProxy(oldOffset, oldLength);
						container.replaceStructuredDocumentRegion(newStructuredDocumentRegion, i);
						return;
					}

					if (content instanceof StructuredDocumentRegionProxy) {
						StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) content;
						if (proxy.getStructuredDocumentRegion() == oldStructuredDocumentRegion) {
							int offset = proxy.getOffset();
							int end = offset + proxy.getLength();
							if (offset >= this.gapOffset) {
								proxy.setOffset(offset + this.diff);
							}
							proxy.setStructuredDocumentRegion(null);
							if (end < oldEnd && (end < this.gapOffset || oldEnd > gapEnd)) { // has
								// shared
								removeStructuredDocumentRegion(oldStructuredDocumentRegion);
								return;
							}
						}
					}
				}
			}
			else {
				throw new StructuredDocumentRegionManagementException();
			}
		}
		else {
			IStructuredDocumentRegion newStructuredDocumentRegion = new StructuredDocumentRegionProxy(oldOffset, oldLength);
			if (ownerEndTag != null) {
				ownerEndTag.setEndStructuredDocumentRegion(newStructuredDocumentRegion);
			}
			else {
				ownerNode.setStructuredDocumentRegion(newStructuredDocumentRegion);
			}
		}
	}

	/**
	 * replaceAttr method
	 * 
	 * @param ownerElement
	 *            org.w3c.dom.Element
	 * @param newAttr
	 *            org.w3c.dom.Attr
	 * @param oldAttr
	 *            org.w3c.dom.Attr
	 */
	void replaceAttr(Element ownerElement, Attr newAttr, Attr oldAttr) {
		if (ownerElement == null)
			return;
		if (getStructuredDocument() == null)
			return;

		ElementImpl element = (ElementImpl) ownerElement;
		if (!element.hasStartTag()) {
			changeStartTag(element);
			return;
		}
		if (element.isCommentTag()) {
			changeStartTag(element);
			return;
		}

		int offset = element.getStartOffset();
		int start = offset;
		int end = offset;

		boolean insertSpace = false;
		String attrValueClose = null;
		if (oldAttr != null) {
			AttrImpl impl = (AttrImpl) oldAttr;
			ITextRegion nameRegion = impl.getNameRegion();
			if (nameRegion == null)
				return; // must never happen
			ITextRegion lastRegion = impl.getValueRegion();
			if (lastRegion != null) {
				end += lastRegion.getEnd();
			}
			else {
				lastRegion = impl.getEqualRegion();
				if (lastRegion != null) {
					end += lastRegion.getEnd();
				}
				else {
					end += nameRegion.getEnd();
					lastRegion = nameRegion;
				}
			}
			// check there are extra space before the last attribute
			IStructuredDocumentRegion flatNode = element.getStartStructuredDocumentRegion();
			if (flatNode == null)
				return; // must never happen
			ITextRegionList regions = flatNode.getRegions();
			if (regions == null)
				return; // must never happen
			ITextRegion prevRegion = null;
			ITextRegion nextRegion = null;
			for (int i = 0; i < regions.size(); i++) {
				ITextRegion region = regions.get(i);
				if (region == nameRegion) {
					if (i > 0) {
						prevRegion = regions.get(i - 1);
					}
				}
				if (region == lastRegion) {
					if (i + 1 < regions.size()) {
						nextRegion = regions.get(i + 1);
					}
					break;
				}
			}
			boolean isLastAttr = false;
			if (nextRegion != null) {
				String regionType = nextRegion.getType();
				if (regionType == DOMRegionContext.XML_TAG_CLOSE || regionType == DOMRegionContext.XML_EMPTY_TAG_CLOSE || isNestedTagClose(regionType)) {
					isLastAttr = true;
				}
			}
			if (isLastAttr && prevRegion != null) {
				start += prevRegion.getTextEnd();
			}
			else {
				start += nameRegion.getStart();
			}

			// impl.resetRegions(ownerElement);
			impl.resetRegions(element);
		}
		else { // append attribute
			IStructuredDocumentRegion flatNode = element.getStartStructuredDocumentRegion();
			if (flatNode == null)
				return; // must never happen

			attrValueClose = getAttrValueClose(element);
			if (attrValueClose != null && attrValueClose.length() > 0) {
				insertSpace = true;
				start = flatNode.getEndOffset();
				end = start;
			}
			else {
				ITextRegionList regions = flatNode.getRegions();
				if (regions == null)
					return; // must never happen
				int attrStart = 0;
				for (int i = regions.size() - 1; i >= 0; i--) {
					ITextRegion region = regions.get(i);
					String regionType = region.getType();
					if (regionType == DOMRegionContext.XML_TAG_CLOSE || regionType == DOMRegionContext.XML_EMPTY_TAG_CLOSE || isNestedTagClose(regionType))
						continue;
					int regionEnd = region.getEnd();
					if (regionEnd == region.getTextEnd())
						insertSpace = true;
					attrStart = regionEnd;
					break;
				}
				if (attrStart == 0)
					return; // not found, must never happen
				start += attrStart;
				end = start;
			}
		}

		String source = null;
		if (newAttr != null) {
			int size = 2;
			if (attrValueClose != null)
				size += attrValueClose.length();
			String name = this.generator.generateAttrName(newAttr);
			if (name != null)
				size += name.length();
			String value = this.generator.generateAttrValue(newAttr);
			if (value != null)
				size += value.length();
			StringBuffer buffer = new StringBuffer(size);
			if (attrValueClose != null)
				buffer.append(attrValueClose);
			if (insertSpace)
				buffer.append(' ');
			buffer.append(name);
			if (value != null) {
				buffer.append('=');
				buffer.append(value);
			}
			source = buffer.toString();
		}

		replaceSource(source, start, end);
	}

	protected boolean isNestedTagClose(String regionType) {
		boolean result = false;
		return result;
	}

	/**
	 * replaceChild method
	 * 
	 * @param parentNode
	 *            org.w3c.dom.Node
	 * @param newChild
	 *            org.w3c.dom.Node
	 * @param oldChild
	 *            org.w3c.dom.Node
	 */
	void replaceChild(Node parentNode, Node newChild, Node oldChild) {
		if (parentNode == null)
			return;
		if (newChild == null && oldChild == null)
			return;
		if (getStructuredDocument() == null)
			return;

		int start = 0;
		int end = 0;
		String preTag = null;
		String postTag = null;
		ElementImpl postElement = null;
		if (oldChild != null) {
			NodeImpl node = (NodeImpl) oldChild;
			start = node.getStartOffset();
			end = node.getEndOffset();
			if (oldChild.getNodeType() == Node.TEXT_NODE) {
				this.gapStructuredDocumentRegion = node.getStructuredDocumentRegion();
			}
			node.resetStructuredDocumentRegions(); // reset values from
			// IStructuredDocumentRegion
		}
		else {
			NodeImpl prev = (NodeImpl) newChild.getPreviousSibling();
			if (prev != null) {
				start = prev.getEndOffset();
				end = start;
				preTag = getCloseTag(prev);
			}
			else {
				// first child
				NodeImpl next = (NodeImpl) newChild.getNextSibling();
				if (next != null) {
					start = next.getStartOffset();
					end = start;
					if (parentNode.getNodeType() == Node.ELEMENT_NODE) {
						preTag = getStartCloseTag((IDOMElement) parentNode);
					}
				}
				else {
					// newly having a child
					if (parentNode.getNodeType() == Node.ELEMENT_NODE) {
						ElementImpl element = (ElementImpl) parentNode;
						if (element.isEmptyTag() || isSelfClosedContainer(element)) { // empty tag format
							// need to generate the start and the end tags
							end = element.getEndOffset();
							start = end - 2; // for "/>"
							element.setEmptyTag(false);
							preTag = this.generator.generateCloseTag(element);
							postTag = this.generator.generateEndTag(element);
							postElement = element;
						}
						else if (!element.hasStartTag()) {
							start = element.getStartOffset();
							end = start;
							// invalid end tag or implicit tag
							// need to generate the start tag
							preTag = this.generator.generateStartTag(element);
							if (preTag != null) {
								int length = preTag.length();
								if (length > 0) {
									IStructuredDocumentRegion flatNode = new StructuredDocumentRegionProxy(start, length);
									element.setStartStructuredDocumentRegion(flatNode);
								}
							}
							if (!element.hasEndTag()) {
								// implicit tag
								// need to generate the end tags
								postTag = this.generator.generateEndTag(element);
								postElement = element;
							}
						}
						else {
							start = element.getStartEndOffset();
							end = start;
							preTag = getStartCloseTag(element);
							if (preTag != null && preTag.length() > 0) {
								if (!element.hasEndTag() && (element.isJSPContainer() || element.isCDATAContainer())) {
									// need to generate the end tag
									postTag = this.generator.generateEndTag(element);
									postElement = element;
								}
							}
						}
					}
					// else might DOCUMENT_NODE, start and end are 0
				}
			}
		}

		String source = null;
		if (newChild != null) {
			StringBuffer buffer = new StringBuffer();
			int offset = start;
			if (preTag != null) {
				int length = preTag.length();
				if (length > 0) {
					offset += length;
					buffer.append(preTag);
				}
			}

			NodeImpl node = (NodeImpl) newChild;
			while (node != null) {
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					ElementImpl element = (ElementImpl) node;
					if (element.preferEmptyTag())
						element.setEmptyTag(true);
					IStructuredDocumentRegion flatNode = null;
					String startTag = this.generator.generateStartTag(element);
					if (startTag != null) {
						int length = startTag.length();
						if (length > 0) {
							buffer.append(startTag);
							flatNode = new StructuredDocumentRegionProxy(offset, length);
							offset += length;
						}
					}
					element.setStartStructuredDocumentRegion(flatNode);
				}
				else {
					String content = this.generator.generateSource(node);
					if (content == null)
						content = NodeImpl.EMPTY_STRING;
					int length = content.length();
					IStructuredDocumentRegion flatNode = null;
					if (length > 0) {
						buffer.append(content);
						flatNode = new StructuredDocumentRegionProxy(offset, length);
						offset += length;
					}
					node.setStructuredDocumentRegion(flatNode);
				}

				NodeImpl child = (NodeImpl) node.getFirstChild();
				if (child != null) {
					node = child;
					continue;
				}

				if (node.getNodeType() == Node.ELEMENT_NODE) {
					ElementImpl element = (ElementImpl) node;
					IStructuredDocumentRegion flatNode = null;
					String endTag = this.generator.generateEndTag(element);
					if (endTag != null) {
						int length = endTag.length();
						if (length > 0) {
							buffer.append(endTag);
							flatNode = new StructuredDocumentRegionProxy(offset, length);
							offset += length;
						}
					}
					element.setEndStructuredDocumentRegion(flatNode);
				}

				while (node != null) {
					if (node == newChild) {
						node = null;
						break;
					}
					NodeImpl next = (NodeImpl) node.getNextSibling();
					if (next != null) {
						node = next;
						break;
					}

					node = (NodeImpl) node.getParentNode();
					if (node.getNodeType() != Node.ELEMENT_NODE)
						continue;
					ElementImpl element = (ElementImpl) node;
					IStructuredDocumentRegion flatNode = null;
					String endTag = this.generator.generateEndTag(element);
					if (endTag != null) {
						int length = endTag.length();
						if (length > 0) {
							buffer.append(endTag);
							flatNode = new StructuredDocumentRegionProxy(offset, length);
							offset += length;
						}
					}
					element.setEndStructuredDocumentRegion(flatNode);
				}
			}

			if (postTag != null) {
				int length = postTag.length();
				if (length > 0) {
					buffer.append(postTag);
					if (postElement != null) {
						IStructuredDocumentRegion flatNode = new StructuredDocumentRegionProxy(offset, length);
						postElement.setEndStructuredDocumentRegion(flatNode);
					}
				}
			}
			source = buffer.toString();
		}

		if (start == end && (source == null || source.length() == 0)) {
			// no thing changed
			return;
		}

		replaceSource(source, start, end);
	}

	void replaceRegions(IStructuredDocumentRegion flatNode, ITextRegionList newRegions, ITextRegionList oldRegions) {
		// future_TODO: optimize

		NodeImpl root = (NodeImpl) this.model.getDocument();
		this.parentNode = root;
		this.nextNode = (NodeImpl) root.getFirstChild();

		removeGapStructuredDocumentRegion(flatNode);
		insertGapStructuredDocumentRegionBefore(flatNode.getStart());
		changeStructuredDocumentRegion(flatNode);
		insertGapStructuredDocumentRegionAfter(flatNode.getEnd());
	}

	/**
	 * Wraps IStructuredDocumentRegion.replaceText() and sets contextual
	 * information.
	 */
	private void replaceSource(String source, int start, int end) {
		int inserted = 0;
		if (source == null)
			source = NodeImpl.EMPTY_STRING;
		else
			inserted = source.length();
		int removed = end - start;
		if (inserted == 0 && removed == 0)
			return;

		this.gapOffset = start;
		this.gapLength = removed;
		this.diff = inserted - removed;
		// Note: due to bug
		// https://w3.opensource.ibm.com/bugzilla/show_bug.cgi?id=3619
		// for now assume "ignore readonly" region is ok -- assume DOM itself
		// checks if
		// ok to insert or not. In reality, we may have to make or "contains"
		// method more
		// better. Or, we may have to "perculate up" the parameter for clients
		// to tell us programatically
		// that its ok to insert/format in a read-only region.
		getStructuredDocument().replaceText(this.model, this.gapOffset, this.gapLength, source, true);
	}

	void replaceStructuredDocumentRegions(IStructuredDocumentRegionList newStructuredDocumentRegions, IStructuredDocumentRegionList oldStructuredDocumentRegions) {
		NodeImpl root = (NodeImpl) this.model.getDocument();

		if (oldStructuredDocumentRegions != null) {
			this.parentNode = root;
			this.nextNode = (NodeImpl) root.getFirstChild();

			Enumeration e = oldStructuredDocumentRegions.elements();
			while (e.hasMoreElements()) {
				IStructuredDocumentRegion flatNode = (IStructuredDocumentRegion) e.nextElement();
				if (flatNode == null)
					continue;
				removeStructuredDocumentRegion(flatNode);
				removeGapStructuredDocumentRegion(flatNode);
			}
		}

		if (newStructuredDocumentRegions != null) {
			this.parentNode = root;
			this.nextNode = (NodeImpl) root.getFirstChild();

			IStructuredDocumentRegion lastStructuredDocumentRegion = null;
			Enumeration e = newStructuredDocumentRegions.elements();
			while (e.hasMoreElements()) {
				IStructuredDocumentRegion flatNode = (IStructuredDocumentRegion) e.nextElement();
				if (flatNode == null)
					continue;
				if (lastStructuredDocumentRegion == null)
					insertGapStructuredDocumentRegionBefore(flatNode.getStart());
				insertStructuredDocumentRegion(flatNode);
				lastStructuredDocumentRegion = flatNode;
			}
			if (lastStructuredDocumentRegion != null) {
				insertGapStructuredDocumentRegionAfter(lastStructuredDocumentRegion.getEnd());
			}
			else {
				insertGapStructuredDocumentRegionBefore(this.gapOffset);
				// make sure to restore all backuped StructuredDocumentRegions
				insertGapStructuredDocumentRegionAfter(this.gapOffset);
			}
		}
		else {
			this.parentNode = root;
			this.nextNode = (NodeImpl) root.getFirstChild();

			insertGapStructuredDocumentRegionBefore(this.gapOffset);
			// make sure to restore all backuped StructuredDocumentRegions
			insertGapStructuredDocumentRegionAfter(this.gapOffset);
		}
	}

	/**
	 */
	private void updateAttrRegions(Element element, IStructuredDocumentRegion flatNode) {

		// update attributes
		ITextRegionList regions = flatNode.getRegions();
		if (regions == null)
			return;
		NamedNodeMap attributes = element.getAttributes();
		if (attributes == null)
			return;
		int index = -1;
		AttrImpl attr = null;
		Iterator e = regions.iterator();
		while (e.hasNext()) {
			ITextRegion region = (ITextRegion) e.next();
			String regionType = region.getType();
			if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
				attr = (AttrImpl) attributes.item(++index);
				if (attr != null) {
					attr.setNameRegion(region);
					// reset other regions
					attr.setEqualRegion(null);
					attr.setValueRegion(null);
				}
			}
			else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) {
				if (attr != null)
					attr.setEqualRegion(region);
			}
			else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) {
				if (attr != null) {
					attr.setValueRegion(region);
					attr = null;
				}
			}
		}
	}
}
