/*******************************************************************************
 * Copyright (c) 2001, 2011 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
 *     
 *     Balazs Banfai: Bug 154737 getUserData/setUserData support for Node
 *     https://bugs.eclipse.org/bugs/show_bug.cgi?id=154737
 *     David Carver (STAR) - bug 296999 - Inefficient use of new String()
 *******************************************************************************/
package org.eclipse.wst.xml.core.internal.document;

import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMText;
import org.eclipse.wst.xml.core.internal.provisional.document.ISourceGenerator;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.w3c.dom.UserDataHandler;

/**
 * TextImpl class
 */
public class TextImpl extends CharacterDataImpl implements IDOMText {

	/**
	 */
	private class StringPair {
		private String fFirst = null;
		private String fSecond = null;

		StringPair(String first, String second) {
			this.fFirst = first;
			this.fSecond = second;
		}

		String getFirst() {
			return this.fFirst;
		}

		String getSecond() {
			return this.fSecond;
		}
	}

	private String fSource = null;

	/**
	 * TextImpl constructor
	 */
	protected TextImpl() {
		super();
	}

	/**
	 * TextImpl constructor
	 * 
	 * @param that
	 *            TextImpl
	 */
	protected TextImpl(TextImpl that) {
		super(that);

		if (that != null) {
			this.fSource = that.getSource();
		}
	}

	/**
	 * appendData method
	 * 
	 * @param arg
	 *            java.lang.String
	 */
	public void appendData(String arg) throws DOMException {
		if (arg == null || arg.length() == 0)
			return;

		if (!isDataEditable()) {
			throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, DOMMessages.NO_MODIFICATION_ALLOWED_ERR);
		}

		String newSource = getSource(arg);
		if (newSource == null)
			return;
		String source = getSource();
		if (source != null)
			setTextSource(source + newSource);
		else
			setTextSource(newSource);
	}

	/**
	 */
	IStructuredDocumentRegion appendStructuredDocumentRegion(IStructuredDocumentRegion newStructuredDocumentRegion) {
		if (newStructuredDocumentRegion == null)
			return null;

		IStructuredDocumentRegion flatNode = getStructuredDocumentRegion();
		if (flatNode == null) {
			setStructuredDocumentRegion(newStructuredDocumentRegion);
			return newStructuredDocumentRegion;
		}

		if (flatNode instanceof StructuredDocumentRegionContainer) {
			StructuredDocumentRegionContainer container = (StructuredDocumentRegionContainer) flatNode;
			container.appendStructuredDocumentRegion(newStructuredDocumentRegion);
		}
		else {
			StructuredDocumentRegionContainer container = new StructuredDocumentRegionContainer();
			container.appendStructuredDocumentRegion(flatNode);
			container.appendStructuredDocumentRegion(newStructuredDocumentRegion);
			setStructuredDocumentRegion(container);
		}

		return newStructuredDocumentRegion;
	}

	/**
	 * appendText method
	 * 
	 * @param text
	 *            org.w3c.dom.Text
	 */
	public void appendText(Text newText) {
		if (newText == null)
			return;

		if (!isDataEditable()) {
			throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, DOMMessages.NO_MODIFICATION_ALLOWED_ERR);
		}

		TextImpl text = (TextImpl) newText;
		String newSource = text.getSource();
		if (newSource == null || newSource.length() == 0)
			return;
		String source = getSource();
		if (source != null)
			setTextSource(source + newSource);
		else
			setTextSource(newSource);
	}

	/**
	 * cloneNode method
	 * 
	 * @return org.w3c.dom.Node
	 * @param deep
	 *            boolean
	 */
	public Node cloneNode(boolean deep) {
		TextImpl cloned = new TextImpl(this);
		notifyUserDataHandlers(UserDataHandler.NODE_CLONED, cloned);
		return cloned;
	}

	/**
	 * deleteData method
	 * 
	 * @param offset
	 *            int
	 * @param count
	 *            int
	 */
	public void deleteData(int offset, int count) throws DOMException {
		if (count == 0)
			return;
		if (!isDataEditable()) {
			throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, DOMMessages.NO_MODIFICATION_ALLOWED_ERR);
		}
		if (count < 0 || offset < 0) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}

		String source = getSource();
		if (source == null || source.length() == 0) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}
		StringPair pair = substringSourceExcluded(source, offset, count);
		if (pair == null)
			return;
		source = null;
		String first = pair.getFirst();
		if (first != null)
			source = first;
		String second = pair.getSecond();
		if (second != null) {
			if (source != null)
				source += second;
			else
				source = second;
		}
		if (source == null)
			source = NodeImpl.EMPTY_STRING; // delete all
		setTextSource(source);
	}

	/**
	 * getData method
	 * 
	 * @return java.lang.String
	 */
	public String getData() throws DOMException {
		if (this.fSource != null)
			return getData(this.fSource);
		String data = super.getData();
		if (data != null)
			return data;
		return getData(getStructuredDocumentRegion());
	}

	/**
	 */
	private String getData(IStructuredDocumentRegion flatNode) {
		if (flatNode == null)
			return NodeImpl.EMPTY_STRING;

		if (flatNode instanceof StructuredDocumentRegionContainer) {
			StructuredDocumentRegionContainer container = (StructuredDocumentRegionContainer) flatNode;
			int length = container.getLength();
			if (length < 16)
				length = 16; // default
			StringBuffer buffer = new StringBuffer(length);
			int count = container.getStructuredDocumentRegionCount();
			for (int i = 0; i < count; i++) {
				IStructuredDocumentRegion content = container.getStructuredDocumentRegion(i);
				String data = getData(content);
				if (data == null)
					continue;
				buffer.append(data);
			}
			return buffer.toString();
		}

		if (flatNode instanceof StructuredDocumentRegionProxy) {
			return flatNode.getText();
		}

		ITextRegion region = StructuredDocumentRegionUtil.getFirstRegion(flatNode);
		if (region != null) {
			String regionType = region.getType();
			if (regionType == DOMRegionContext.XML_ENTITY_REFERENCE || regionType == DOMRegionContext.XML_CHAR_REFERENCE) {
				String name = StructuredDocumentRegionUtil.getEntityRefName(flatNode, region);
				if (name != null) {
					DocumentImpl document = (DocumentImpl) getOwnerDocument();
					if (document != null) {
						String value = document.getCharValue(name);
						if (value != null)
							return value;
					}
				}
			}
		}

		return flatNode.getText();
	}

	/**
	 * Returns data for the source
	 */
	private String getData(String source) {
		if (source == null)
			return null;
		StringBuffer buffer = null;
		int offset = 0;
		int length = source.length();
		int ref = source.indexOf('&');
		while (ref >= 0) {
			int end = source.indexOf(';', ref + 1);
			if (end > ref + 1) {
				String name = source.substring(ref + 1, end);
				String value = getCharValue(name);
				if (value != null) {
					if (buffer == null)
						buffer = new StringBuffer(length);
					if (ref > offset)
						buffer.append(source.substring(offset, ref));
					buffer.append(value);
					offset = end + 1;
					ref = end;
				}
			}
			ref = source.indexOf('&', ref + 1);
		}
		if (buffer == null)
			return source;
		if (length > offset)
			buffer.append(source.substring(offset));
		return buffer.toString();
	}

	/**
	 * getFirstStructuredDocumentRegion method
	 * 
	 */
	public IStructuredDocumentRegion getFirstStructuredDocumentRegion() {
		return StructuredDocumentRegionUtil.getFirstStructuredDocumentRegion(getStructuredDocumentRegion());
	}

	/**
	 * getLastStructuredDocumentRegion method
	 * 
	 */
	public IStructuredDocumentRegion getLastStructuredDocumentRegion() {
		return StructuredDocumentRegionUtil.getLastStructuredDocumentRegion(getStructuredDocumentRegion());
	}

	/**
	 * getNodeName method
	 * 
	 * @return java.lang.String
	 */
	public String getNodeName() {
		return "#text";//$NON-NLS-1$
	}

	/* (non-Javadoc)
	 * @see org.w3c.dom.Node#getNodeType()
	 */
	public short getNodeType() {
		return TEXT_NODE;
	}

	/**
	 */
	public String getSource() {
		if (this.fSource != null)
			return this.fSource;
		String data = super.getData();
		if (data != null && data.length() > 0) {
			String source = getSource(data);
			if (source != null)
				return source;
		}
		return super.getSource();
	}

	/**
	 * Returns source for the data
	 */
	private String getSource(String data) {
		if (data == null)
			return null;
		IDOMModel model = getModel();
		if (model == null)
			return null; // error
		ISourceGenerator generator = model.getGenerator();
		if (generator == null)
			return null; // error
		return generator.generateTextData(this, data);
	}

	/**
	 */
	String getTextSource() {
		return this.fSource;
	}

	/**
	 */
	public String getValueSource() {
		return getSource();
	}

	/**
	 */
	boolean hasStructuredDocumentRegion(IStructuredDocumentRegion askedStructuredDocumentRegion) {
		if (askedStructuredDocumentRegion == null)
			return false;

		IStructuredDocumentRegion flatNode = getStructuredDocumentRegion();
		if (flatNode == null)
			return false;

		if (flatNode == askedStructuredDocumentRegion)
			return true;

		if (flatNode instanceof StructuredDocumentRegionProxy) {
			StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) flatNode;
			if (proxy.getStructuredDocumentRegion() == askedStructuredDocumentRegion)
				return true;
			return false;
		}

		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;
				if (content == askedStructuredDocumentRegion)
					return true;
				if (content instanceof StructuredDocumentRegionProxy) {
					StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) content;
					if (proxy.getStructuredDocumentRegion() == askedStructuredDocumentRegion)
						return true;
				}
			}
			return false;
		}

		return false;
	}

	/**
	 * insertData method
	 * 
	 * @param offset
	 *            int
	 * @param arg
	 *            java.lang.String
	 */
	public void insertData(int offset, String arg) throws DOMException {
		if (arg == null || arg.length() == 0)
			return;
		if (!isDataEditable()) {
			throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, DOMMessages.NO_MODIFICATION_ALLOWED_ERR);
		}
		if (offset < 0) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}

		String source = getSource();
		if (source == null || source.length() == 0) {
			if (offset > 0) {
				throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
			}
			source = getSource(arg);
			if (source != null)
				setTextSource(source);
			return;
		}

		StringPair pair = substringSourceExcluded(source, offset, 0);
		if (pair == null)
			return; // error
		StringBuffer buffer = new StringBuffer(source.length() + arg.length());
		String first = pair.getFirst();
		if (first != null)
			buffer.append(first);
		source = getSource(arg);
		if (source != null)
			buffer.append(source);
		String second = pair.getSecond();
		if (second != null)
			buffer.append(second);
		setTextSource(buffer.toString());
	}

	/**
	 */
	IStructuredDocumentRegion insertStructuredDocumentRegion(IStructuredDocumentRegion newStructuredDocumentRegion, IStructuredDocumentRegion nextStructuredDocumentRegion) {
		if (newStructuredDocumentRegion == null)
			return null;
		if (nextStructuredDocumentRegion == null)
			return appendStructuredDocumentRegion(newStructuredDocumentRegion);

		IStructuredDocumentRegion flatNode = getStructuredDocumentRegion();
		if (flatNode == null)
			return null; // error

		if (flatNode == nextStructuredDocumentRegion) {
			StructuredDocumentRegionContainer container = new StructuredDocumentRegionContainer();
			container.appendStructuredDocumentRegion(newStructuredDocumentRegion);
			container.appendStructuredDocumentRegion(flatNode);
			setStructuredDocumentRegion(container);
			return newStructuredDocumentRegion;
		}

		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 == nextStructuredDocumentRegion) {
					container.insertStructuredDocumentRegion(newStructuredDocumentRegion, i);
					return newStructuredDocumentRegion;
				}
			}
			return null; // error
		}

		return null; // error
	}

	/**
	 * insertText method
	 * 
	 * @param text
	 *            org.w3c.dom.Text
	 * @param offset
	 *            int
	 */
	public void insertText(Text newText, int offset) throws DOMException {
		if (newText == null)
			return;
		if (!isDataEditable()) {
			throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, DOMMessages.NO_MODIFICATION_ALLOWED_ERR);
		}
		TextImpl text = (TextImpl) newText;
		String newSource = text.getSource();
		if (newSource == null || newSource.length() == 0)
			return;
		if (offset < 0) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}

		String source = getSource();
		if (source == null || source.length() == 0) {
			if (offset > 0) {
				throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
			}
			setTextSource(newSource);
			return;
		}

		StringPair pair = substringSourceExcluded(source, offset, 0);
		if (pair == null)
			return; // error
		StringBuffer buffer = new StringBuffer(source.length() + newSource.length());
		String first = pair.getFirst();
		if (first != null)
			buffer.append(first);
		buffer.append(newSource);
		String second = pair.getSecond();
		if (second != null)
			buffer.append(second);
		setTextSource(buffer.toString());
	}

	/**
	 * isCDATAContent method
	 * 
	 * @return boolean
	 */
	public boolean isCDATAContent() {
		Node parent = getParentNode();
		if (parent == null || parent.getNodeType() != Node.ELEMENT_NODE)
			return false;
		ElementImpl element = (ElementImpl) parent;
		return element.isCDATAContainer();
	}

	/**
	 */
	public boolean isInvalid() {
		return isInvalid(getStructuredDocumentRegion());
	}

	/**
	 */
	private boolean isInvalid(IStructuredDocumentRegion flatNode) {
		if (flatNode == null)
			return false;

		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 (isInvalid(content))
					return true;
			}
			return false;
		}

		if (flatNode instanceof StructuredDocumentRegionProxy) {
			StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) flatNode;
			return isInvalid(proxy.getStructuredDocumentRegion());
		}

		String regionType = StructuredDocumentRegionUtil.getFirstRegionType(flatNode);
		if (regionType != DOMRegionContext.XML_CONTENT && isNotNestedContent(regionType) && regionType != DOMRegionContext.XML_ENTITY_REFERENCE && regionType != DOMRegionContext.XML_CHAR_REFERENCE && regionType != DOMRegionContext.BLOCK_TEXT && regionType != DOMRegionContext.WHITE_SPACE) {
			return true;
		}

		return false;
	}

	protected boolean isNotNestedContent(String regionType) {
		boolean result = true;
		return result;
	}

	/**
	 */
	boolean isSharingStructuredDocumentRegion(IStructuredDocumentRegion sharedStructuredDocumentRegion) {
		if (sharedStructuredDocumentRegion == null)
			return false;

		IStructuredDocumentRegion flatNode = getStructuredDocumentRegion();
		if (flatNode == null)
			return false;

		if (flatNode == sharedStructuredDocumentRegion)
			return false;

		if (flatNode instanceof StructuredDocumentRegionProxy) {
			StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) flatNode;
			if (proxy.getStructuredDocumentRegion() == sharedStructuredDocumentRegion)
				return true;
			return false;
		}

		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;
				if (content == sharedStructuredDocumentRegion)
					return false;
				if (content instanceof StructuredDocumentRegionProxy) {
					StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) content;
					if (proxy.getStructuredDocumentRegion() == sharedStructuredDocumentRegion)
						return true;
				}
			}
			return false;
		}

		return false;
	}

	/**
	 * Returns whether this text node contains <a
	 * href='http://www.w3.org/TR/2004/REC-xml-infoset-20040204#infoitem.character'>
	 * element content whitespace</a>, often abusively called "ignorable
	 * whitespace". The text node is determined to contain whitespace in
	 * element content during the load of the document or if validation occurs
	 * while using <code>Document.normalizeDocument()</code>.
	 * 
	 * @see DOM Level 3
	 */
	public boolean isElementContentWhitespace() {
		return isWhitespace();
	}

	/**
	 */
	public boolean isWhitespace() {
		String data = getData();
		if (data == null)
			return true;
		int length = data.length();
		for (int i = 0; i < length; i++) {
			if (!Character.isWhitespace(data.charAt(i)))
				return false;
		}
		return true;
	}

	/**
	 */
	IStructuredDocumentRegion removeStructuredDocumentRegion(IStructuredDocumentRegion oldStructuredDocumentRegion) {
		if (oldStructuredDocumentRegion == null)
			return null;

		IStructuredDocumentRegion flatNode = getStructuredDocumentRegion();
		if (flatNode == null)
			return null; // error

		if (flatNode == oldStructuredDocumentRegion) {
			setStructuredDocumentRegion(null);
			return oldStructuredDocumentRegion;
		}

		if (flatNode instanceof StructuredDocumentRegionProxy) {
			StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) flatNode;
			if (proxy.getStructuredDocumentRegion() == oldStructuredDocumentRegion) {
				// removed with proxy
				setStructuredDocumentRegion(null);
				return oldStructuredDocumentRegion;
			}
			return null; // error
		}

		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 == oldStructuredDocumentRegion) {
					container.removeStructuredDocumentRegion(i);
					if (container.getStructuredDocumentRegionCount() == 1) {
						// get back to single IStructuredDocumentRegion
						setStructuredDocumentRegion(container.getStructuredDocumentRegion(0));
					}
					return oldStructuredDocumentRegion;
				}

				if (content instanceof StructuredDocumentRegionProxy) {
					StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) content;
					if (proxy.getStructuredDocumentRegion() == oldStructuredDocumentRegion) {
						// removed with proxy
						container.removeStructuredDocumentRegion(i);
						if (container.getStructuredDocumentRegionCount() == 1) {
							// get back to single IStructuredDocumentRegion
							setStructuredDocumentRegion(container.getStructuredDocumentRegion(0));
						}
						return oldStructuredDocumentRegion;
					}
				}
			}
			return null; // error
		}

		return null; // error
	}

	/**
	 * replaceData method
	 * 
	 * @param offset
	 *            int
	 * @param count
	 *            int
	 * @param arg
	 *            java.lang.String
	 */
	public void replaceData(int offset, int count, String arg) throws DOMException {
		if (!isDataEditable()) {
			throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, DOMMessages.NO_MODIFICATION_ALLOWED_ERR);
		}
		if (arg == null || arg.length() == 0) {
			deleteData(offset, count);
			return;
		}
		if (count == 0) {
			insertData(offset, arg);
			return;
		}
		if (offset < 0 || count < 0) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}

		String source = getSource();
		if (source == null || source.length() == 0) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}

		StringPair pair = substringSourceExcluded(source, offset, count);
		if (pair == null)
			return; // error
		StringBuffer buffer = new StringBuffer(source.length() + arg.length());
		String first = pair.getFirst();
		if (first != null)
			buffer.append(first);
		source = getSource(arg);
		if (source != null)
			buffer.append(source);
		String second = pair.getSecond();
		if (second != null)
			buffer.append(second);
		setTextSource(buffer.toString());
	}

	/**
	 */
	IStructuredDocumentRegion replaceStructuredDocumentRegion(IStructuredDocumentRegion newStructuredDocumentRegion, IStructuredDocumentRegion oldStructuredDocumentRegion) {
		if (oldStructuredDocumentRegion == null)
			return null;
		if (newStructuredDocumentRegion == null)
			return removeStructuredDocumentRegion(oldStructuredDocumentRegion);

		IStructuredDocumentRegion flatNode = getStructuredDocumentRegion();
		if (flatNode == null)
			return null; // error

		if (flatNode == oldStructuredDocumentRegion) {
			setStructuredDocumentRegion(newStructuredDocumentRegion);
			return oldStructuredDocumentRegion;
		}

		if (flatNode instanceof StructuredDocumentRegionProxy) {
			StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) flatNode;
			if (proxy.getStructuredDocumentRegion() == oldStructuredDocumentRegion) {
				if (newStructuredDocumentRegion instanceof StructuredDocumentRegionProxy) {
					// proxy must not be nested
					setStructuredDocumentRegion(newStructuredDocumentRegion);
				}
				else {
					proxy.setStructuredDocumentRegion(newStructuredDocumentRegion);
				}
				return oldStructuredDocumentRegion;
			}
			return null; // error
		}

		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) {
					container.replaceStructuredDocumentRegion(newStructuredDocumentRegion, i);
					return oldStructuredDocumentRegion;
				}

				if (content instanceof StructuredDocumentRegionProxy) {
					StructuredDocumentRegionProxy proxy = (StructuredDocumentRegionProxy) content;
					if (proxy.getStructuredDocumentRegion() == oldStructuredDocumentRegion) {
						if (newStructuredDocumentRegion instanceof StructuredDocumentRegionProxy) {
							// proxy must not be nested
							container.replaceStructuredDocumentRegion(newStructuredDocumentRegion, i);
						}
						else {
							proxy.setStructuredDocumentRegion(newStructuredDocumentRegion);
						}
						return oldStructuredDocumentRegion;
					}
				}
			}
			return null; // error
		}

		return null; // error
	}

	/**
	 */
	void resetStructuredDocumentRegions() {
		String source = getSource();
		if (source != null && source.length() > 0)
			this.fSource = source;
		super.resetStructuredDocumentRegions();
	}

	/**
	 * getData method
	 * 
	 * @return java.lang.String
	 */
	public void setData(String data) throws DOMException {
		if (!isDataEditable()) {
			throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, DOMMessages.NO_MODIFICATION_ALLOWED_ERR);
		}

		this.fSource = null;
		super.setData(data);
	}

	/**
	 */
	public void setSource(String source) throws InvalidCharacterException {
		if (!isDataEditable()) {
			throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, DOMMessages.NO_MODIFICATION_ALLOWED_ERR);
		}

		SourceValidator validator = new SourceValidator(this);
		if (validator.validateSource(source))
			setTextSource(source);
	}

	/**
	 */
	void setStructuredDocumentRegion(IStructuredDocumentRegion flatNode) {
		super.setStructuredDocumentRegion(flatNode);
		if (flatNode != null)
			this.fSource = null;
	}

	/**
	 */
	public void setTextSource(String source) {
		if (!isDataEditable()) {
			throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, DOMMessages.NO_MODIFICATION_ALLOWED_ERR);
		}

		this.fSource = source;

		notifyValueChanged();
	}

	/**
	 */
	public void setValueSource(String source) {
		if (!isDataEditable()) {
			throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, DOMMessages.NO_MODIFICATION_ALLOWED_ERR);
		}

		SourceValidator validator = new SourceValidator(this);
		setTextSource(validator.convertSource(source));
	}

	/**
	 * splitText method
	 * 
	 * @return org.w3c.dom.Text
	 * @param offset
	 *            int
	 */
	public Text splitText(int offset) throws DOMException {
		if (!isDataEditable()) {
			throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, DOMMessages.NO_MODIFICATION_ALLOWED_ERR);
		}
		if (offset < 0) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}
		int length = getLength();
		if (offset > length) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}
		Document document = getOwnerDocument();
		if (document == null)
			return null;

		String source = null;
		if (offset < length) {
			int count = length - offset;
			source = substringSource(offset, count);
			deleteData(offset, count);
		}
		TextImpl text = (TextImpl) document.createTextNode(null);
		if (source != null)
			text.setTextSource(source);

		Node parent = getParentNode();
		if (parent != null)
			parent.insertBefore(text, getNextSibling());

		return text;
	}

	/**
	 */
	Text splitText(IStructuredDocumentRegion nextStructuredDocumentRegion) {
		if (nextStructuredDocumentRegion == null)
			return null;

		IStructuredDocumentRegion flatNode = getStructuredDocumentRegion();
		if (flatNode == null || !(flatNode instanceof StructuredDocumentRegionContainer))
			return null; // error

		StructuredDocumentRegionContainer container = (StructuredDocumentRegionContainer) flatNode;
		int count = container.getStructuredDocumentRegionCount();
		int index = 0;
		for (; index < count; index++) {
			if (container.getStructuredDocumentRegion(index) == nextStructuredDocumentRegion)
				break;
		}
		if (index >= count) {
			// this is the case nextStructuredDocumentRegion is a new
			// IStructuredDocumentRegion
			// search gap by offset
			int offset = nextStructuredDocumentRegion.getStart();
			for (index = 0; index < count; index++) {
				IStructuredDocumentRegion content = container.getStructuredDocumentRegion(index);
				if (content == null)
					continue; // error
				if (content.getStart() >= offset)
					break;
			}
			if (index >= count)
				return null; // error
		}
		if (index == 0)
			return this; // nothing to do

		Document document = getOwnerDocument();
		if (document == null)
			return null; // error
		Node parent = getParentNode();
		if (parent == null)
			return null; // error
		TextImpl nextText = (TextImpl) document.createTextNode(null);
		if (nextText == null)
			return null; // error

		for (; index < count; count--) {
			nextText.appendStructuredDocumentRegion(container.removeStructuredDocumentRegion(index));
		}

		// normalize IStructuredDocumentRegion
		if (index == 1) {
			setStructuredDocumentRegion(container.getStructuredDocumentRegion(0));
		}

		parent.insertBefore(nextText, getNextSibling());
		return nextText;
	}

	/**
	 * Retruns data for the range
	 */
	private String substringData(String data, int offset, int count) throws DOMException {
		// sure offset and count are non-negative
		if (count == 0)
			return NodeImpl.EMPTY_STRING;
		if (data == null) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}
		int length = data.length();
		if (offset > length) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}
		int end = offset + count;
		if (end > length) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}
		return data.substring(offset, end);
	}

	/**
	 * Returns source for the range specified by: offset: data offset count:
	 * data count
	 */
	private String substringSource(int offset, int count) throws DOMException {
		// sure offset and count are non-negative
		if (this.fSource != null)
			return substringSource(this.fSource, offset, count);

		String data = super.getData();
		if (data != null && data.length() > 0) {
			data = substringData(data, offset, count);
			if (data == null)
				return NodeImpl.EMPTY_STRING;
			String source = getSource(data);
			if (source != null)
				return source;
		}

		return substringSource(getSource(), offset, count);
	}

	/**
	 * Returns source for the range specified by: offset: data offset count:
	 * data count
	 */
	private String substringSource(String source, int offset, int count) throws DOMException {
		// sure offset and count are non-negative
		if (count == 0)
			return NodeImpl.EMPTY_STRING;
		if (source == null) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}

		int length = source.length();
		int end = offset + count;

		// find character reference
		int ref = source.indexOf('&');
		while (ref >= 0) {
			if (ref >= end)
				break;
			int refEnd = source.indexOf(';', ref + 1);
			if (refEnd > ref + 1) {
				String name = source.substring(ref + 1, refEnd);
				if (getCharValue(name) != null) {
					// found, shift for source offsets
					int refCount = refEnd - ref;
					if (ref < offset)
						offset += refCount;
					if (ref < end)
						end += refCount;
					ref = refEnd;
				}
			}
			ref = source.indexOf('&', ref + 1);
		}

		if (offset > length || end > length) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}

		return source.substring(offset, end);
	}

	/**
	 * Returns sources before and after the range specified by: offset: data
	 * offset count: data count
	 */
	private StringPair substringSourceExcluded(String source, int offset, int count) throws DOMException {
		// sure offset and count are non-negative
		if (source == null) {
			if (offset == 0 && count == 0)
				return new StringPair(null, null);
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}

		int length = source.length();
		int end = offset + count;

		// find character reference
		int ref = source.indexOf('&');
		while (ref >= 0) {
			if (ref >= end)
				break;
			int refEnd = source.indexOf(';', ref + 1);
			if (refEnd > ref + 1) {
				String name = source.substring(ref + 1, refEnd);
				if (getCharValue(name) != null) {
					// found, shift for source offsets
					int refCount = refEnd - ref;
					if (ref < offset)
						offset += refCount;
					if (ref < end)
						end += refCount;
					ref = refEnd;
				}
			}
			ref = source.indexOf('&', ref + 1);
		}

		if (offset > length) {
			throw new DOMException(DOMException.INDEX_SIZE_ERR, DOMMessages.INDEX_SIZE_ERR);
		}

		String first = (offset > 0 ? source.substring(0, offset) : null);
		String second = (end < length ? source.substring(end, length) : null);
		return new StringPair(first, second);
	}

	/* (non-Javadoc)
	 * @see org.w3c.dom.Text#getWholeText()
	 */
	public String getWholeText() {
		Node current = this;
		// rewind
		while (current.getPreviousSibling() != null)
			current = current.getPreviousSibling();
		// playback
		StringBuffer buffer = new StringBuffer();
		while (current != null) {
			if (current.getNodeType() == Node.TEXT_NODE || current.getNodeType() == Node.CDATA_SECTION_NODE) {
				buffer.append(current.getNodeValue());
			}
			current = current.getNextSibling();
		}
		return buffer.toString();
	}
    /**
	 * Replaces the text of the current node and all logically-adjacent text
	 * nodes with the specified text. All logically-adjacent text nodes are
	 * removed including the current node unless it was the recipient of the
	 * replacement text. <br>
	 * This method returns the node which received the replacement text. The
	 * returned node is:
	 * <ul>
	 * <li><code>null</code>, when the replacement text is the empty
	 * string; </li>
	 * <li>the current node, except when the current node is read-only; </li>
	 * <li> a new <code>Text</code> node of the same type (
	 * <code>Text</code> or <code>CDATASection</code>) as the current
	 * node inserted at the location of the replacement. </li>
	 * </ul>
	 * <br>
	 * For instance, in the above example calling
	 * <code>replaceWholeText</code> on the <code>Text</code> node that
	 * contains "bar" with "yo" in argument results in the following: <br>
	 * Where the nodes to be removed are read-only descendants of an
	 * <code>EntityReference</code>, the <code>EntityReference</code>
	 * must be removed instead of the read-only nodes. If any
	 * <code>EntityReference</code> to be removed has descendants that are
	 * not <code>EntityReference</code>, <code>Text</code>, or
	 * <code>CDATASection</code> nodes, the <code>replaceWholeText</code>
	 * method must fail before performing any modification of the document,
	 * raising a <code>DOMException</code> with the code
	 * <code>NO_MODIFICATION_ALLOWED_ERR</code>. <br>
	 * For instance, in the example below calling
	 * <code>replaceWholeText</code> on the <code>Text</code> node that
	 * contains "bar" fails, because the <code>EntityReference</code> node
	 * "ent" contains an <code>Element</code> node which cannot be removed.
	 * 
	 * @param content
	 *            The content of the replacing <code>Text</code> node.
	 * @return The <code>Text</code> node created with the specified
	 *         content.
	 * @exception DOMException
	 *                NO_MODIFICATION_ALLOWED_ERR: Raised if one of the
	 *                <code>Text</code> nodes being replaced is readonly.
	 * @see DOM Level 3
	 */
    public Text replaceWholeText(String content)
                                 throws DOMException {
		throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Not implemented"); //$NON-NLS-1$
    }
}
