/*******************************************************************************
 * Copyright (c) 2010, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.xml.ui.internal.contentassist;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;

import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.graphics.Image;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
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.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.sse.core.internal.util.Debug;
import org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext;
import org.eclipse.wst.sse.ui.internal.contentassist.ContentAssistUtils;
import org.eclipse.wst.sse.ui.internal.contentassist.CustomCompletionProposal;
import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDataType;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument;
import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMEntityDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNode;
import org.eclipse.wst.xml.core.internal.contentmodel.basic.CMNamedNodeMapImpl;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQuery;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQueryAction;
import org.eclipse.wst.xml.core.internal.contentmodel.util.DOMNamespaceHelper;
import org.eclipse.wst.xml.core.internal.document.AttrImpl;
import org.eclipse.wst.xml.core.internal.modelquery.ModelQueryUtil;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
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.regions.DOMRegionContext;
import org.eclipse.wst.xml.ui.internal.XMLUIMessages;
import org.eclipse.wst.xml.ui.internal.XMLUIPlugin;
import org.eclipse.wst.xml.ui.internal.editor.CMImageUtil;
import org.eclipse.wst.xml.ui.internal.editor.XMLEditorPluginImageHelper;
import org.eclipse.wst.xml.ui.internal.editor.XMLEditorPluginImages;
import org.eclipse.wst.xml.ui.internal.preferences.XMLUIPreferenceNames;
import org.eclipse.wst.xml.ui.internal.taginfo.MarkupTagInfoProvider;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;


/**
 * <p>Implementation of an {@link AbstractXMLCompletionProposalComputer} that uses {@link ModelQuery}s
 * to make its proposals.</p>
 * 
 * @base org.eclipse.wst.xml.ui.internal.contentassist.AbstractContentAssistProcessor
 */
public abstract class AbstractXMLModelQueryCompletionProposalComputer extends AbstractXMLCompletionProposalComputer {
	
	private static MarkupTagInfoProvider infoProvider = new MarkupTagInfoProvider();
	
	/**
	 * <p>Default constructor</p>
	 */
	public AbstractXMLModelQueryCompletionProposalComputer() {
	}
	
	/**
	 * <p>default is to do nothing</p>
	 * 
	 * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#sessionEnded()
	 */
	public void sessionEnded() {
		//default is to do nothing
	}

	/**
	 * <p>default is to do nothing</p>
	 * 
	 * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#sessionStarted()
	 */
	public void sessionStarted() {
		//default is to do nothing
	}
	
	/**
	 * @return {@link XMLContentModelGenerator} used to generate proposals
	 */
	protected abstract XMLContentModelGenerator getContentGenerator();
	
	/**
	 * <p>Given a {@link CMNode} generated by a model query should decide if the
	 * action is valid for this implementation of the model query proposal computer</p>
	 * 
	 * <p>This is needed because {@link ModelQuery}s return a lot of {@link CMNode}s that
	 * can come from multiple sources and a particular computer may not want to propose
	 * all of the actions as content assist proposals</p>
	 * 
	 * @param action {@link CMNode} to decide if it is valid as a result
	 * for this model query proposal computer
	 * 
	 * @return <code>true</code> if the given {@link CMNode} is valid for this
	 * computer, <code>false</code> otherwise
	 */
	protected abstract boolean validModelQueryNode(CMNode node);
	
	protected void addAttributeNameProposals(
			ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context) {
		
		IDOMNode node = (IDOMNode) contentAssistRequest.getNode();
		IStructuredDocumentRegion sdRegion = contentAssistRequest.getDocumentRegion();
		// retrieve the list of attributes
		CMElementDeclaration elementDecl = getCMElementDeclaration(node);
		if (elementDecl != null) {
			CMNamedNodeMapImpl attributes = new CMNamedNodeMapImpl(elementDecl.getAttributes());
			addModelQueryAttributeDeclarations(node, elementDecl,attributes);

			String matchString = contentAssistRequest.getMatchString();
			int cursorOffset = context.getInvocationOffset();
			// check whether an attribute really exists for the replacement
			// offsets AND if it possesses a value
			boolean attrAtLocationHasValue = false;
			boolean proposalNeedsSpace = false;
			NamedNodeMap attrs = node.getAttributes();
			for (int i = 0; i < attrs.getLength(); i++) {
				AttrImpl existingAttr = (AttrImpl) attrs.item(i);
				ITextRegion name = existingAttr.getNameRegion();
				
				if (name != null && (sdRegion.getStartOffset(name) <= contentAssistRequest.getReplacementBeginPosition()) &&
						(sdRegion.getStartOffset(name) + name.getLength() >= contentAssistRequest.getReplacementBeginPosition() + contentAssistRequest.getReplacementLength()) &&
						(existingAttr.getValueRegion() != null)) {
					// selected region is attribute name
					if (cursorOffset >= sdRegion.getStartOffset(name) && contentAssistRequest.getReplacementLength() != 0)
						attrAtLocationHasValue = true;
					// propose new attribute, cursor is at the start of another attribute name
					else if (cursorOffset == sdRegion.getStartOffset(name))
						proposalNeedsSpace = true;
					break;
				}
			}

			// only add proposals for the attributes whose names begin with the matchstring
			if (attributes != null) {
				for (int i = 0; i < attributes.getLength(); i++) {
					CMAttributeDeclaration attrDecl = (CMAttributeDeclaration) attributes.item(i);

					if(validModelQueryNode(attrDecl)) {
						int isRequired = 0;
						if (attrDecl.getUsage() == CMAttributeDeclaration.REQUIRED) {
							isRequired = XMLRelevanceConstants.R_REQUIRED;
						}
	
						boolean showAttribute = true;
						showAttribute = showAttribute && beginsWith(getRequiredName(node, attrDecl), matchString.trim());
						AttrImpl attr = (AttrImpl) node.getAttributes().getNamedItem(getRequiredName(node, attrDecl));
						ITextRegion nameRegion = attr != null ? attr.getNameRegion() : null;
						// nameRegion.getEndOffset() + 1 is required to allow for
						// matches against the full name of an existing Attr
						showAttribute = showAttribute && ((attr == null) || nameRegion == null ||
								((nameRegion != null) &&
										(sdRegion.getStartOffset(nameRegion) <
											contentAssistRequest.getReplacementBeginPosition()) &&
										(sdRegion.getStartOffset(nameRegion) + nameRegion.getLength() >=
											(contentAssistRequest.getReplacementBeginPosition() +
											contentAssistRequest.getReplacementLength()) )));
						if (showAttribute) {
							//get the proposal image
							Image attrImage = CMImageUtil.getImage(attrDecl);
							if (attrImage == null) {
								if (isRequired > 0) {
									attrImage = this.getRequiredAttributeImage();
								} else {
									attrImage = this.getNotRequiredAttributeImage();
								}
							}
	
							String proposedText = null;
							String proposedInfo = getAdditionalInfo(elementDecl, attrDecl);
							CustomCompletionProposal proposal = null;
							// attribute is at this location and already exists
							if (attrAtLocationHasValue) {
								// only propose the name
								proposedText = getRequiredName(node, attrDecl);
								proposal = new MarkupCompletionProposal(
										proposedText, contentAssistRequest.getReplacementBeginPosition(),
										contentAssistRequest.getReplacementLength(), proposedText.length(),
										attrImage, proposedText, null, proposedInfo,
										XMLRelevanceConstants.R_XML_ATTRIBUTE_NAME + isRequired, true);
							}
							// no attribute exists or is elsewhere, generate
							// minimally
							else {
								Attr existingAttrNode = (Attr) node.getAttributes().getNamedItem(getRequiredName(node, attrDecl));
								String value = null;
								if (existingAttrNode != null && existingAttrNode.getSpecified()) {
									value = existingAttrNode.getNodeValue();
								}
								int cursorPosition = 0;
								if ((value != null) && (value.length() > 0)) {
									proposedText = getRequiredName(node, attrDecl);
									cursorPosition = proposedText.length() + 2;
								}
								else {
									proposedText = getRequiredText(node, attrDecl);
									// skip the cursor past a fixed value
									if (attrDecl.getAttrType() != null && attrDecl.getAttrType().getImpliedValueKind() == CMDataType.IMPLIED_VALUE_FIXED)
										cursorPosition = proposedText.length();
									else
										cursorPosition = getRequiredName(node, attrDecl).length() + 2;
								}
								if (proposalNeedsSpace)
									proposedText += " "; //$NON-NLS-1$
								proposal = new MarkupCompletionProposal(proposedText,
										contentAssistRequest.getReplacementBeginPosition(),
										contentAssistRequest.getReplacementLength(),
										cursorPosition, attrImage,
								// if the value isn't empty (no empty set of quotes), show it
								// BUG 203494, content strings may have "", but not be empty
								// An empty string is when there's no content between double quotes
								// and there is no single quote that may be encasing a double quote
										((proposedText.lastIndexOf('\"') - proposedText.indexOf('\"') ==  1 &&
												proposedText.indexOf('\'') == -1)) ? getRequiredName(node, attrDecl) : proposedText,
										null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_NAME + isRequired);
							}
							contentAssistRequest.addProposal(proposal);
						}
					}
				}
			}
		}
		else {
			setErrorMessage(NLS.bind(XMLUIMessages.Element__is_unknown, (new Object[]{node.getNodeName()})));
		}
	}
	
	protected void addAttributeValueProposals(
			ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context) {
		
		IDOMNode node = (IDOMNode) contentAssistRequest.getNode();

		// Find the attribute region and name for which this position should
		// have a value proposed
		IStructuredDocumentRegion open = node.getFirstStructuredDocumentRegion();
		ITextRegionList openRegions = open.getRegions();
		int i = openRegions.indexOf(contentAssistRequest.getRegion());
		if (i < 0) {
			return;
		}
		ITextRegion nameRegion = null;
		while (i >= 0) {
			nameRegion = openRegions.get(i--);
			if (nameRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
				break;
			}
		}

		// the name region is REQUIRED to do anything useful
		if (nameRegion != null) {
			// Retrieve the declaration
			CMElementDeclaration elementDecl = getCMElementDeclaration(node);

			// String attributeName = nameRegion.getText();
			String attributeName = open.getText(nameRegion);

			CMAttributeDeclaration attrDecl = null;

			// No CMElementDeclaration means no attribute metadata, but retrieve the
			// declaration for the attribute otherwise
			if (elementDecl != null) {
				CMNamedNodeMapImpl allAttributes = new CMNamedNodeMapImpl(elementDecl.getAttributes()) {
					private Map caseInsensitive;
					
					private Map getCaseInsensitiveMap() {
						if(caseInsensitive == null)
							caseInsensitive = new HashMap();
						return caseInsensitive;
					}

					public CMNode getNamedItem(String name) {
						CMNode node = super.getNamedItem(name);
						if (node == null) {
							node = (CMNode) getCaseInsensitiveMap().get(name.toLowerCase(Locale.US));
						}
						return node;
					}

					public void put(CMNode cmNode) {
						super.put(cmNode);
						getCaseInsensitiveMap().put(cmNode.getNodeName().toLowerCase(Locale.US), cmNode);
					}
				};
				this.addModelQueryAttributeDeclarations(node, elementDecl, allAttributes);

				String noprefixName = DOMNamespaceHelper.getUnprefixedName(attributeName);
				if (allAttributes != null) {
					attrDecl = (CMAttributeDeclaration) allAttributes.getNamedItem(attributeName);
					if (attrDecl == null) {
						attrDecl = (CMAttributeDeclaration) allAttributes.getNamedItem(noprefixName);
					}
				}
				if (attrDecl == null) {
					setErrorMessage(XMLUIMessages.No_known_attribute__UI_ + attributeName);
				}
			}

			String currentValue = node.getAttributes().getNamedItem(attributeName).getNodeValue();
			String proposedInfo = null;
			//get proposal image
			Image image = CMImageUtil.getImage(attrDecl);
			if (image == null) {
				if ((attrDecl != null) && (attrDecl.getUsage() == CMAttributeDeclaration.REQUIRED)) {
					image = this.getRequiredAttributeImage();
				} else {
					image = this.getNotRequiredAttributeImage();
				}
			}

			if ((attrDecl != null) && (attrDecl.getAttrType() != null)) {
				// attribute is known, prompt with values from the declaration
				proposedInfo = getAdditionalInfo(elementDecl, attrDecl);
				List possibleValues = getPossibleDataTypeValues(node, attrDecl);
				String defaultValue = attrDecl.getAttrType().getImpliedValue();
				String qualifiedDelimiter = (String) attrDecl.getProperty("qualified-delimiter"); //$NON-NLS-1$
				if (possibleValues.size() > 0 || defaultValue != null) {
					// ENUMERATED VALUES
					String matchString = contentAssistRequest.getMatchString();
					if (matchString == null) {
						matchString = ""; //$NON-NLS-1$
					}
					if ((matchString.length() > 0) && (matchString.startsWith("\"") || matchString.startsWith("'"))) { //$NON-NLS-1$ //$NON-NLS-2$
						matchString = matchString.substring(1);
					}
					boolean currentValid = false;

					//create suggestions for enumerated values
					int rOffset = contentAssistRequest.getReplacementBeginPosition();
					int rLength = contentAssistRequest.getReplacementLength();
					for (Iterator j = possibleValues.iterator(); j.hasNext();) {
						String possibleValue = (String) j.next();
						String alternateMatch = null;
						if (qualifiedDelimiter != null) {
							int delimiter = possibleValue.lastIndexOf(qualifiedDelimiter);
							if (delimiter >= 0 && delimiter < possibleValue.length() - 1) {
								alternateMatch = possibleValue.substring(delimiter + 1);
							}
						}
						if(!possibleValue.equals(defaultValue)) {
							currentValid = currentValid || possibleValue.equals(currentValue);
							if ((matchString.length() == 0) || possibleValue.startsWith(matchString)) {
								String rString = "\"" + possibleValue + "\""; //$NON-NLS-1$ //$NON-NLS-2$
								alternateMatch = "\"" + alternateMatch; //$NON-NLS-1$
								CustomCompletionProposal proposal = new MarkupCompletionProposal(
										rString, rOffset, rLength, possibleValue.length() + 1,
										XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_ENUM),
										rString, alternateMatch, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE, true);
								contentAssistRequest.addProposal(proposal);
							}
						}
					}
					if(defaultValue != null && ((matchString.length() == 0) || defaultValue.startsWith(matchString))) {
						String rString = "\"" + defaultValue + "\""; //$NON-NLS-1$ //$NON-NLS-2$
						final String regionText = contentAssistRequest.getDocumentRegion().getText(contentAssistRequest.getRegion());
						final int matchStringLength = contentAssistRequest.getMatchString().length();
						if (matchString.length() > 0 && matchStringLength < regionText.length()) {
							final String remaining = regionText.substring(matchStringLength).trim();
							if (remaining.charAt(0) != '\'' && remaining.charAt(0) != '"') {
								rLength = matchStringLength;
							}
						}
						CustomCompletionProposal proposal = new MarkupCompletionProposal(
								rString, rOffset, rLength, defaultValue.length() + 1,
								XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_DEFAULT),
								rString, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE);
						contentAssistRequest.addProposal(proposal);
					}
				}
				else if (((attrDecl.getUsage() == CMAttributeDeclaration.FIXED) ||
						(attrDecl.getAttrType().getImpliedValueKind() == CMDataType.IMPLIED_VALUE_FIXED)) &&
						(attrDecl.getAttrType().getImpliedValue() != null)) {
					
					// FIXED values
					String value = attrDecl.getAttrType().getImpliedValue();
					if ((value != null) && (value.length() > 0)) {
						String rValue = "\"" + value + "\"";//$NON-NLS-2$//$NON-NLS-1$
						CustomCompletionProposal proposal = new MarkupCompletionProposal(
								rValue, contentAssistRequest.getReplacementBeginPosition(),
								contentAssistRequest.getReplacementLength(), rValue.length() + 1,
								image, rValue, null, proposedInfo,
								XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE);
						contentAssistRequest.addProposal(proposal);
						if ((currentValue.length() > 0) && !value.equals(currentValue)) {
							rValue = "\"" + currentValue + "\""; //$NON-NLS-2$//$NON-NLS-1$
							proposal = new MarkupCompletionProposal(rValue, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), rValue.length() + 1, image, rValue, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE);
							contentAssistRequest.addProposal(proposal);
						}
					}
				}
			}
			else {
				// unknown attribute, so supply nice empty values
				proposedInfo = getAdditionalInfo(null, elementDecl);
				CustomCompletionProposal proposal = null;
				if ((currentValue != null) && (currentValue.length() > 0)) {
					final String regionText = open.getText(contentAssistRequest.getRegion());
					if (regionText.charAt(0) != '"' && regionText.charAt(0) != '\'') {
						String rValue = "\"" + currentValue + "\""; //$NON-NLS-2$//$NON-NLS-1$
						proposal = new MarkupCompletionProposal(rValue,
								contentAssistRequest.getReplacementBeginPosition(),
								contentAssistRequest.getReplacementLength(), 1, image,
								rValue, null, proposedInfo,
								XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE);
						contentAssistRequest.addProposal(proposal);
					}
				}
			}
		}
		else {
			setErrorMessage(XMLUIMessages.Content_Assist_not_availab_UI_);
		}
	}
	
	protected void addCommentProposal(
			ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context) {
		
		contentAssistRequest.addProposal(new CustomCompletionProposal("<!--  -->", //$NON-NLS-1$
				contentAssistRequest.getReplacementBeginPosition(),
				contentAssistRequest.getReplacementLength(), 5,
				XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_COMMENT),
				NLS.bind(XMLUIMessages.Comment__, (new Object[]{" <!--  -->"})), //$NON-NLS-1$
				null, null, XMLRelevanceConstants.R_COMMENT));
	}
	
	

	/**
	 * Add the proposals for the name in an end tag
	 */
	protected void addEndTagNameProposals(ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context) {

		if (contentAssistRequest.getStartOffset() + contentAssistRequest.getRegion().getTextLength() < contentAssistRequest.getReplacementBeginPosition()) {
			CustomCompletionProposal proposal = new MarkupCompletionProposal(">", //$NON-NLS-1$
						contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), 1, XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_TAG_GENERIC), NLS.bind(XMLUIMessages.Close_with__, (new Object[]{" '>'"})), //$NON-NLS-1$
						null, null, XMLRelevanceConstants.R_END_TAG_NAME);
			contentAssistRequest.addProposal(proposal);
		}
		else {
			Node aNode = contentAssistRequest.getNode();
			String matchString = contentAssistRequest.getMatchString();
			if (matchString.startsWith("</")) { //$NON-NLS-1$
				matchString = matchString.substring(2);
			}
			while (aNode != null) {
				if (aNode.getNodeType() == Node.ELEMENT_NODE) {
					if (aNode.getNodeName().startsWith(matchString)) {
						IDOMNode aXMLNode = (IDOMNode) aNode;
						CMElementDeclaration ed = getCMElementDeclaration(aNode);
						//declaration must be valid for this computer to make proposal
						if ((aXMLNode.getEndStructuredDocumentRegion() == null) && (ed == null || (validModelQueryNode(ed) && ed.getContentType() != CMElementDeclaration.EMPTY))) {
							String replacementText = aNode.getNodeName();
							String displayText = replacementText;
							String proposedInfo = (ed != null) ? getAdditionalInfo(null, ed) : null;
							if(!contentAssistRequest.getDocumentRegion().isEnded()) {
								replacementText += ">"; //$NON-NLS-1$
							}
							CustomCompletionProposal proposal = null;
							// double check to see if the region acted upon is
							// a tag name; replace it if so
							Image image = CMImageUtil.getImage(ed);
							if (image == null) {
								image = this.getGenericTagImage();
							}
							if (contentAssistRequest.getRegion().getType() == DOMRegionContext.XML_TAG_NAME) {
								proposal = new MarkupCompletionProposal(
										replacementText, contentAssistRequest.getStartOffset(),
										contentAssistRequest.getRegion().getTextLength(),
										replacementText.length(), image, displayText, null,
										proposedInfo, XMLRelevanceConstants.R_END_TAG_NAME);
							}
							else {
								proposal = new MarkupCompletionProposal(
										replacementText,
										contentAssistRequest.getReplacementBeginPosition(),
										contentAssistRequest.getReplacementLength(),
										replacementText.length(), image,
										NLS.bind(XMLUIMessages.Close_with__,
												(new Object[]{"'" + displayText + "'"})), //$NON-NLS-1$ //$NON-NLS-2$
										null, proposedInfo, XMLRelevanceConstants.R_END_TAG_NAME);
							}
							contentAssistRequest.addProposal(proposal);
						}
					}
				}
				aNode = aNode.getParentNode();
			}
		}
	}

	/**
	 * Prompt for end tags to a non-empty Node that hasn't ended Handles these
	 * cases: <br>
	 * <tagOpen>| <br>
	 * <tagOpen>< |<br>
	 * <tagOpen></ |
	 * 
	 * @param contentAssistRequest
	 */
	protected void addEndTagProposals(
			ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context) {
		
		IDOMNode node = (IDOMNode) contentAssistRequest.getParent();

		if (isCommentNode(node)) {
			// loop and find non comment node parent
			while ((node != null) && isCommentNode(node)) {
				node = (IDOMNode) node.getParentNode();
			}
		}

		// node is already closed
		if (node.isClosed()) {
			// loop and find non comment unclose node parent
			while ((node != null) && node.isClosed()) {
				node = (IDOMNode) node.getParentNode();
			}
		}
		// there were no unclosed tags
		if (node == null) {
			return;
		}

		// data to create a CustomCompletionProposal
		String replaceText = node.getNodeName() + ">"; //$NON-NLS-1$
		int replaceBegin = contentAssistRequest.getReplacementBeginPosition();
		int replaceLength = contentAssistRequest.getReplacementLength();
		int cursorOffset = node.getNodeName().length() + 1;
		String displayString = ""; //$NON-NLS-1$
		String proposedInfo = ""; //$NON-NLS-1$
		Image image = this.getGenericTagImage();

		setErrorMessage(null);
		boolean addProposal = false;

		if (node.getNodeType() == Node.ELEMENT_NODE) {
			// ////////////////////////////////////////////////////////////////////////////////////
			IStructuredDocument sDoc = (IStructuredDocument) context.getDocument();
			IStructuredDocumentRegion xmlEndTagOpen = sDoc.getRegionAtCharacterOffset(contentAssistRequest.getReplacementBeginPosition());
			// skip backward to "<", "</", or the (unclosed) start tag, null if not found
			String type = ""; //$NON-NLS-1$
			while ((xmlEndTagOpen != null) &&
					((type = xmlEndTagOpen.getType()) != DOMRegionContext.XML_END_TAG_OPEN) &&
					(type != DOMRegionContext.XML_TAG_CLOSE) && !needsEndTag(xmlEndTagOpen, context) &&
					(type != DOMRegionContext.XML_TAG_OPEN)) {
				
				xmlEndTagOpen = xmlEndTagOpen.getPrevious();
			}

			if (xmlEndTagOpen == null) {
				return;
			}

			node = (IDOMNode) node.getModel().getIndexedRegion(xmlEndTagOpen.getStartOffset());
			node = (IDOMNode) node.getParentNode();

			if (isStartTag(xmlEndTagOpen)) {
				// this is the case for a start tag w/out end tag
				// eg:
				// <p>
				// <% String test = "test"; %>
				// |
				if (needsEndTag(xmlEndTagOpen, context)) {
					String tagName = getTagName(xmlEndTagOpen);
					xmlEndTagOpen.getTextEndOffset();
					replaceLength = 0;
					replaceText = "</" + tagName + ">"; //$NON-NLS-1$ //$NON-NLS-2$ $NON-NLS-2$
					cursorOffset = tagName.length() + 3;
					displayString = NLS.bind(XMLUIMessages.End_with__, (new Object[]{tagName}));
					addProposal = true;
				}
			}
			else if (type == DOMRegionContext.XML_END_TAG_OPEN) {
				// this is the case for: <tag> </ |
				// possibly <tag> </ |<anotherTag>
				// should only be replacing white space...
				replaceLength = (replaceBegin > xmlEndTagOpen.getTextEndOffset()) ? replaceBegin - xmlEndTagOpen.getTextEndOffset() : 0;
				replaceText = node.getNodeName() + ">"; //$NON-NLS-1$
				cursorOffset = replaceText.length();
				replaceBegin = xmlEndTagOpen.getTextEndOffset();
				displayString = NLS.bind(XMLUIMessages.End_with_, (new Object[]{node.getNodeName()}));
				addProposal = true;
			}
			else if (type == DOMRegionContext.XML_TAG_OPEN) {
				// this is the case for: <tag> < |
				replaceText = "/" + node.getNodeName() + ">"; //$NON-NLS-1$ //$NON-NLS-2$ $NON-NLS-2$
				cursorOffset = replaceText.length();

				// should only be replacing white space...
				replaceLength = (replaceBegin > xmlEndTagOpen.getTextEndOffset()) ? replaceBegin - xmlEndTagOpen.getTextEndOffset() : 0;
				replaceBegin = xmlEndTagOpen.getTextEndOffset();
				displayString = NLS.bind(XMLUIMessages.End_with_, (new Object[]{"/" + node.getNodeName()})); //$NON-NLS-1$
				addProposal = true;
			}
		}
		// ////////////////////////////////////////////////////////////////////////////////////
		// sometimes the node is not null, but
		// getNodeValue() is null, put in a null check
		else if ((node.getNodeValue() != null) && (node.getNodeValue().indexOf("</") != -1)) { //$NON-NLS-1$
			// the case where "</" is started, but the nodes comes in as a
			// text node (instead of element)
			// like this: <tag> </|
			Node parent = node.getParentNode();
			if ((parent != null) && (parent.getNodeType() != Node.DOCUMENT_NODE)) {
				replaceText = parent.getNodeName() + ">"; //$NON-NLS-1$
				cursorOffset = replaceText.length();
				displayString = NLS.bind(XMLUIMessages.End_with__, (new Object[]{parent.getNodeName()}));
				setErrorMessage(null);
				addProposal = true;
			}
		}
		// ////////////////////////////////////////////////////////////////////////////////////
		else if (node.getNodeType() == Node.DOCUMENT_NODE) {
			setErrorMessage(XMLUIMessages.Content_Assist_not_availab_UI_);
		}
		if (addProposal == true) {
			CustomCompletionProposal proposal = new MarkupCompletionProposal(replaceText, replaceBegin, replaceLength, cursorOffset, image, displayString, null, proposedInfo, XMLRelevanceConstants.R_END_TAG);
			contentAssistRequest.addProposal(proposal);
		}
	}
	
	protected void addEntityProposals(
			ContentAssistRequest contentAssistRequest,
			ITextRegion completionRegion, IDOMNode treeNode,
			CompletionProposalInvocationContext context) {
		
		ICompletionProposal[] eps = computeEntityReferenceProposals(completionRegion,
				treeNode, context);
		for (int i = 0; (eps != null) && (i < eps.length); i++) {
			contentAssistRequest.addProposal(eps[i]);
		}
	}
	
	protected void addEntityProposals(Vector proposals, Properties map,
			String key, int nodeOffset, IStructuredDocumentRegion sdRegion,
			ITextRegion completionRegion,
			CompletionProposalInvocationContext context) {
		
		if (map == null) {
			return;
		}
		String entityName = ""; //$NON-NLS-1$
		String entityValue = ""; //$NON-NLS-1$
		Image entityIcon = this.getEntityReferenceImage();
		String replacementText = ""; //$NON-NLS-1$
		String displayString = ""; //$NON-NLS-1$
		Enumeration keys = map.keys();

		while ((keys != null) && keys.hasMoreElements()) {
			entityName = (String) keys.nextElement();
			entityValue = map.getProperty(entityName);
			// filter based on partial entity string...
			if (entityName.toLowerCase().startsWith(key.toLowerCase()) || key.trim().equals("")) //$NON-NLS-1$
			{
				// figure out selection...if text is selected, add it to
				// selection length
				int selectionLength = nodeOffset;
				if (context.getViewer() != null) {
					selectionLength += context.getViewer().getSelectedRange().y;
				}
				// create a new proposal for entity string...
				replacementText = "&" + entityName + ";"; //$NON-NLS-1$ //$NON-NLS-2$ 
				displayString = "&" + entityName + "; (" + entityValue + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				ICompletionProposal cp = new CustomCompletionProposal(replacementText, sdRegion.getStartOffset(completionRegion), selectionLength, replacementText.length(), entityIcon, displayString, null, null, XMLRelevanceConstants.R_ENTITY);
				if (cp != null) {
					proposals.add(cp);
				}
			}
		}
	}
	
	protected void addPCDATAProposal(String nodeName,
			ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context) {
		
		CustomCompletionProposal proposal = new CustomCompletionProposal("<![CDATA[]]>", //$NON-NLS-1$
					contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), 9, XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_CDATASECTION), "CDATA Section", //$NON-NLS-1$
					null, null, XMLRelevanceConstants.R_CDATA);
		contentAssistRequest.addProposal(proposal);

		proposal = new CustomCompletionProposal(nodeName, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), nodeName.length(), XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_TXTEXT), "#PCDATA", //$NON-NLS-1$
					null, null, XMLRelevanceConstants.R_CDATA);
		contentAssistRequest.addProposal(proposal);
	}
	
	protected void addStartDocumentProposals(
			ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context) {
		
		//determine if XMLPI is first element
		Node aNode = contentAssistRequest.getNode();
		Document owningDocument = aNode.getOwnerDocument();
		Node first = owningDocument.getFirstChild();
		boolean xmlpiIsFirstElement = ((first != null) && (first.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE));
		
		// make sure xmlpi is root element don't want doctype proposal if XMLPI isn't first element...
		if (xmlpiIsFirstElement && (owningDocument.getDoctype() == null) &&
				isCursorAfterXMLPI(contentAssistRequest)) {
			
			addDocTypeProposal(contentAssistRequest, context);
		}
	}
	
	/**
	 * Close an unclosed start tag
	 */
	protected void addTagCloseProposals(
			ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context) {
		
		IDOMNode node = (IDOMNode) contentAssistRequest.getParent();
		if (node.getNodeType() == Node.ELEMENT_NODE) {

			CMElementDeclaration elementDecl = getCMElementDeclaration(node);
			String proposedInfo = (elementDecl != null) ? getAdditionalInfo(null, elementDecl) : null;
			int contentType = (elementDecl != null) ? elementDecl.getContentType() : CMElementDeclaration.ANY;
			// if it's XML and content doesn't HAVE to be element, add "/>" proposal.
			boolean endWithSlashBracket = (isXMLNode(node) && (contentType != CMElementDeclaration.ELEMENT));

			//get the image
			Image image = CMImageUtil.getImage(elementDecl);
			if (image == null) {
				image = this.getGenericTagImage();
			}

			// is the start tag ended properly?
			if ((contentAssistRequest.getDocumentRegion() == node.getFirstStructuredDocumentRegion()) && !(node.getFirstStructuredDocumentRegion()).isEnded()) {
				setErrorMessage(null);
				// Is this supposed to be an empty tag? Note that if we can't
				// tell, we assume it's not.
				if ((elementDecl != null) && (elementDecl.getContentType() == CMElementDeclaration.EMPTY)) {
					// prompt with a self-closing end character if needed
					// this is one of the few times to ignore the length -- always insert
					// contentAssistRequest.getReplacementLength()
					CustomCompletionProposal proposal = new MarkupCompletionProposal(
							getContentGenerator().getStartTagClose(node, elementDecl),
							contentAssistRequest.getReplacementBeginPosition(), 0,
							getContentGenerator().getStartTagClose(node, elementDecl).length(), image,
							NLS.bind(XMLUIMessages.Close_with___,(new Object[]{getContentGenerator().getStartTagClose(node, elementDecl)})),
							null, proposedInfo, XMLRelevanceConstants.R_CLOSE_TAG);
					contentAssistRequest.addProposal(proposal);
				}
				else {
					// prompt with a close for the start tag
					CustomCompletionProposal proposal = new MarkupCompletionProposal(">", //$NON-NLS-1$
								contentAssistRequest.getReplacementBeginPosition(),
								// this is one of the few times to ignore the
								// length -- always insert
								// contentAssistRequest.getReplacementLength(),
								0, 1, image, NLS.bind(XMLUIMessages.Close_with__, (new Object[]{" '>'"})), //$NON-NLS-1$
								null, proposedInfo, XMLRelevanceConstants.R_CLOSE_TAG);
					contentAssistRequest.addProposal(proposal);

					// prompt with the closer for the start tag and an end tag if one is not present
					if (node.getEndStructuredDocumentRegion() == null) {
						// make sure tag name is actually what it thinks it
						// is...(eg. <%@ vs. <jsp:directive)
						IStructuredDocumentRegion sdr = contentAssistRequest.getDocumentRegion();
						String openingTagText = (sdr != null) ? sdr.getFullText() : ""; //$NON-NLS-1$
						if ((openingTagText != null) && (openingTagText.indexOf(node.getNodeName()) != -1)) {
							proposal = new MarkupCompletionProposal("></" + node.getNodeName() + ">", //$NON-NLS-2$//$NON-NLS-1$
										contentAssistRequest.getReplacementBeginPosition(),
										// this is one of the few times to
										// ignore the length -- always insert
										// contentAssistRequest.getReplacementLength(),
										0, 1, image, NLS.bind(XMLUIMessages.Close_with____, (new Object[]{node.getNodeName()})), null, proposedInfo, XMLRelevanceConstants.R_CLOSE_TAG);
							contentAssistRequest.addProposal(proposal);
						}
					}
					// prompt with slash bracket "/>" incase if it's a self ending tag
					if (endWithSlashBracket) {
						proposal = new MarkupCompletionProposal("/>", //$NON-NLS-1$
									contentAssistRequest.getReplacementBeginPosition(),
									// this is one of the few times to ignore
									// the length -- always insert
									// contentAssistRequest.getReplacementLength(),
									0, 2, image, NLS.bind(XMLUIMessages.Close_with__, (new Object[]{" \"/>\""})), //$NON-NLS-1$
									null, proposedInfo, XMLRelevanceConstants.R_CLOSE_TAG + 1); // +1
						// to bring to top of list
						contentAssistRequest.addProposal(proposal);
					}
				}
			}
			else if ((contentAssistRequest.getDocumentRegion() == node.getLastStructuredDocumentRegion()) && !node.getLastStructuredDocumentRegion().isEnded()) {
				setErrorMessage(null);
				// prompt with a closing end character for the end tag
				CustomCompletionProposal proposal = new MarkupCompletionProposal(">", //$NON-NLS-1$
							contentAssistRequest.getReplacementBeginPosition(),
							// this is one of the few times to ignore the length -- always insert
							// contentAssistRequest.getReplacementLength(),
							0, 1, image, NLS.bind(XMLUIMessages.Close_with__, (new Object[]{" '>'"})), //$NON-NLS-1$
							null, proposedInfo, XMLRelevanceConstants.R_CLOSE_TAG);
				contentAssistRequest.addProposal(proposal);
			}
		}
		else if (node.getNodeType() == Node.DOCUMENT_NODE) {
			setErrorMessage(XMLUIMessages.Content_Assist_not_availab_UI_);
		}
	}
	
	protected void addTagInsertionProposals(
			ContentAssistRequest contentAssistRequest, int childPosition,
			CompletionProposalInvocationContext context) {
		
		List cmnodes = null;
		Node parent = contentAssistRequest.getParent();
		String error = null;

		// (nsd) This is only valid at the document element level
		// only valid if it's XML (check added 2/17/2004)
		if ((parent != null) && (parent.getNodeType() == Node.DOCUMENT_NODE) &&
				((IDOMDocument) parent).isXMLType() && !isCursorAfterXMLPI(contentAssistRequest)) {
			return;
		}
		// only want proposals if cursor is after doctype...
		if (!isCursorAfterDoctype(contentAssistRequest)) {
			return;
		}

		// fix for meta-info comment nodes.. they currently "hide" other
		// proposals because the don't
		// have a content model (so can't propose any children..)
		if ((parent != null) && (parent instanceof IDOMNode) && isCommentNode((IDOMNode) parent)) {
			// loop and find non comment node?
			while ((parent != null) && isCommentNode((IDOMNode) parent)) {
				parent = parent.getParentNode();
			}
		}

		if (parent.getNodeType() == Node.ELEMENT_NODE) {
			CMElementDeclaration parentDecl = getCMElementDeclaration(parent);
			if (parentDecl != null) {
				// XSD-specific ability - no filtering
				CMDataType childType = parentDecl.getDataType();
				if (childType != null) {
					String[] childStrings = childType.getEnumeratedValues();
					String defaultValue = childType.getImpliedValue();
					if (childStrings != null || defaultValue != null) {
						// the content string is the sole valid child...so replace the rest
						int begin = contentAssistRequest.getReplacementBeginPosition();
						int length = contentAssistRequest.getReplacementLength();
						if (parent instanceof IDOMNode) {
							if (((IDOMNode) parent).getLastStructuredDocumentRegion() != ((IDOMNode) parent).getFirstStructuredDocumentRegion()) {
								begin = ((IDOMNode) parent).getFirstStructuredDocumentRegion().getEndOffset();
								length = ((IDOMNode) parent).getLastStructuredDocumentRegion().getStartOffset() - begin;
							}
						}
						String proposedInfo = getAdditionalInfo(parentDecl, childType);
						for (int i = 0; i < childStrings.length; i++) {
							if(!childStrings[i].equals(defaultValue)) {
								CustomCompletionProposal textProposal = new MarkupCompletionProposal(
										childStrings[i],begin, length, childStrings[i].length(),
										XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_ENUM),
										childStrings[i], null, proposedInfo, XMLRelevanceConstants.R_TAG_INSERTION);
								contentAssistRequest.addProposal(textProposal);
							}
						}
						if(defaultValue != null) {
							CustomCompletionProposal textProposal = new MarkupCompletionProposal(
									defaultValue, begin, length, defaultValue.length(),
									XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_DEFAULT),
									defaultValue, null, proposedInfo, XMLRelevanceConstants.R_TAG_INSERTION);
							contentAssistRequest.addProposal(textProposal);
						}
					}
				}
			}
			if ((parentDecl != null) && (parentDecl.getContentType() == CMElementDeclaration.PCDATA)) {
				addPCDATAProposal(parentDecl.getNodeName(), contentAssistRequest, context);
			}
			else {
				// retrieve the list of all possible children within this parent context
				cmnodes = getAvailableChildElementDeclarations((Element) parent, childPosition,ModelQueryAction.INSERT);

				// retrieve the list of the possible children within this
				// parent context and at this index
				List strictCMNodeSuggestions = null;
				if (XMLUIPreferenceNames.SUGGESTION_STRATEGY_VALUE_STRICT.equals(XMLUIPlugin.getInstance().getPreferenceStore().getString(XMLUIPreferenceNames.SUGGESTION_STRATEGY))) {
					strictCMNodeSuggestions = getValidChildElementDeclarations((Element) parent, childPosition, ModelQueryAction.INSERT);
				}
				Iterator nodeIterator = cmnodes.iterator();
				if (!nodeIterator.hasNext()) {
					if (getCMElementDeclaration(parent) != null) {
						error = NLS.bind(XMLUIMessages._Has_no_available_child, (new Object[]{parent.getNodeName()}));
					}
					else {
						error = NLS.bind(XMLUIMessages.Element__is_unknown, (new Object[]{parent.getNodeName()}));
					}
				}
				String matchString = contentAssistRequest.getMatchString();
				// chop off any leading <'s and whitespace from the matchstring
				while ((matchString.length() > 0) &&
						(Character.isWhitespace(matchString.charAt(0)) || beginsWith(matchString, "<"))) { //$NON-NLS-1$
					
					matchString = matchString.substring(1);
				}
				while (nodeIterator.hasNext()) {
					Object o = nodeIterator.next();
					if (o instanceof CMElementDeclaration) {
						CMElementDeclaration elementDecl =(CMElementDeclaration) o;
						// only add proposals for the child element's that
						// begin with the matchstring
						String tagname = getRequiredName(parent, elementDecl);
						boolean isStrictCMNodeSuggestion =
							strictCMNodeSuggestions != null ? strictCMNodeSuggestions.contains(elementDecl) : false;

						//get the proposal image
						Image image = CMImageUtil.getImage(elementDecl);
						if (image == null) {
							if (strictCMNodeSuggestions != null) {
								image = isStrictCMNodeSuggestion ? this.getEmphasizedTagImage() : this.getDeemphasizedTagImage();
							} else {
								image = this.getGenericTagImage();
							}
						}

						if (beginsWith(tagname, matchString)) {
							String proposedText = getRequiredText(parent, elementDecl);

							// https://bugs.eclipse.org/bugs/show_bug.cgi?id=89811
							// place cursor in first empty quotes
							int markupAdjustment = getCursorPositionForProposedText(proposedText);

							String proposedInfo = getAdditionalInfo(parentDecl, elementDecl);
							int relevance = isStrictCMNodeSuggestion ? XMLRelevanceConstants.R_STRICTLY_VALID_TAG_INSERTION : XMLRelevanceConstants.R_TAG_INSERTION;
							CustomCompletionProposal proposal = new MarkupCompletionProposal(
									proposedText, contentAssistRequest.getReplacementBeginPosition(),
									contentAssistRequest.getReplacementLength(), markupAdjustment,
									image, tagname, null, proposedInfo, relevance);
							contentAssistRequest.addProposal(proposal);
						}
					}
				}
				if (contentAssistRequest.getProposals().size() == 0) {
					if (error != null) {
						setErrorMessage(error);
					}
					else if ((contentAssistRequest.getMatchString() != null) &&
							(contentAssistRequest.getMatchString().length() > 0)) {
						
						setErrorMessage(NLS.bind(
								XMLUIMessages.No_known_child_tag,
								(new Object[]{parent.getNodeName(), contentAssistRequest.getMatchString()})));
					}
					else {
						setErrorMessage(NLS.bind(
								XMLUIMessages.__Has_no_known_child,
								(new Object[]{parent.getNodeName()})));
					}
				}
			}
		}
		else if (parent.getNodeType() == Node.DOCUMENT_NODE) {
			// Can only prompt with elements if the cursor position is past
			// the XML processing
			// instruction and DOCTYPE declaration
			boolean xmlpiFound = false;
			boolean doctypeFound = false;
			int minimumOffset = -1;

			for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {

				boolean xmlpi = ((child.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) && child.getNodeName().equals("xml")); //$NON-NLS-1$
				boolean doctype = child.getNodeType() == Node.DOCUMENT_TYPE_NODE;
				if (xmlpi || (doctype && (minimumOffset < 0))) {
					minimumOffset = ((IDOMNode) child).getFirstStructuredDocumentRegion().getStartOffset() + ((IDOMNode) child).getFirstStructuredDocumentRegion().getTextLength();
				}
				xmlpiFound = xmlpiFound || xmlpi;
				doctypeFound = doctypeFound || doctype;
			}

			if (contentAssistRequest.getReplacementBeginPosition() >= minimumOffset) {
				List childDecls = getAvailableRootChildren((Document) parent, childPosition);
				for (int i = 0; i < childDecls.size(); i++) {
					CMElementDeclaration ed = (CMElementDeclaration) childDecls.get(i);
					if (ed != null) {
						Image image = CMImageUtil.getImage(ed);
						if (image == null) {
							image = this.getGenericTagImage();
						}
						String proposedText = getRequiredText(parent, ed);
						String tagname = getRequiredName(parent, ed);
						// account for the &lt; and &gt;
						int markupAdjustment = getContentGenerator().getMinimalStartTagLength(parent, ed);
						String proposedInfo = getAdditionalInfo(null, ed);
						CustomCompletionProposal proposal = new MarkupCompletionProposal(
								proposedText, contentAssistRequest.getReplacementBeginPosition(),
								contentAssistRequest.getReplacementLength(), markupAdjustment, image,
								tagname, null, proposedInfo, XMLRelevanceConstants.R_TAG_INSERTION);
						contentAssistRequest.addProposal(proposal);
					}
				}
			}
		}
	}

	protected void addTagNameProposals(
			ContentAssistRequest contentAssistRequest, int childPosition,
			CompletionProposalInvocationContext context) {
		
		List cmnodes = null;
		Node parent = contentAssistRequest.getParent();
		IDOMNode node = (IDOMNode) contentAssistRequest.getNode();
		String error = null;
		String matchString = contentAssistRequest.getMatchString();
		if (parent.getNodeType() == Node.ELEMENT_NODE) {
			// retrieve the list of children
			// validActions = getAvailableChildrenAtIndex((Element) parent,
			// childPosition);
			cmnodes = getAvailableChildElementDeclarations((Element) parent, childPosition, ModelQueryAction.INSERT);
			List strictCMNodeSuggestions = null;
			if (XMLUIPreferenceNames.SUGGESTION_STRATEGY_VALUE_STRICT.equals(XMLUIPlugin.getInstance().getPreferenceStore().getString(XMLUIPreferenceNames.SUGGESTION_STRATEGY))) {
				strictCMNodeSuggestions = getValidChildElementDeclarations((Element) parent, childPosition, ModelQueryAction.INSERT);
			}
			
			Iterator nodeIterator = cmnodes.iterator();
			// chop off any leading <'s and whitespace from the matchstring
			while ((matchString.length() > 0) &&
					(Character.isWhitespace(matchString.charAt(0)) || beginsWith(matchString, "<"))) { //$NON-NLS-1$
				
				matchString = matchString.substring(1);
			}
			if (!nodeIterator.hasNext()) {
				error = NLS.bind(XMLUIMessages.__Has_no_known_child, (new Object[]{parent.getNodeName()}));
			}
			while (nodeIterator.hasNext()) {
				CMNode elementDecl = (CMNode) nodeIterator.next();
				if (elementDecl != null) {
					// only add proposals for the child element's that begin with the matchstring
					String proposedText = null;
					int cursorAdjustment = 0;
					
					//determine if strict suggestion
					boolean isStrictCMNodeSuggestion =
						strictCMNodeSuggestions != null ? strictCMNodeSuggestions.contains(elementDecl) : false;

					// do a check to see if partial attributes of partial tag names are in list
					if ((contentAssistRequest.documentRegion.getStartOffset() < context.getInvocationOffset()) && (((node != null) && (node.getAttributes() != null) &&
							(node.getAttributes().getLength() > 0) &&
							attributeInList(node, parent, elementDecl)) ||
							((node.getNodeType() != Node.TEXT_NODE) &&
									node.getFirstStructuredDocumentRegion().isEnded()))) {

						proposedText = getRequiredName(parent, elementDecl);
						cursorAdjustment = proposedText.length();
					}
					else {
						proposedText = getRequiredName(parent, elementDecl);
						
						cursorAdjustment = proposedText.length();
						if (elementDecl instanceof CMElementDeclaration) {
							CMElementDeclaration ed = (CMElementDeclaration) elementDecl;
							// https://bugs.eclipse.org/bugs/show_bug.cgi?id=89811
							StringBuffer sb = new StringBuffer();
							getContentGenerator().generateTag(parent, ed, sb);
							// since it's a name proposal, assume '<' is already there
							// only return the rest of the tag
							proposedText = sb.toString().substring(1);
							cursorAdjustment = getCursorPositionForProposedText(proposedText);
						}
					}
					if (beginsWith(proposedText, matchString)) {
						//get the proposal image
						Image image = CMImageUtil.getImage(elementDecl);
						if (image == null) {
							if (strictCMNodeSuggestions != null) {
								image = isStrictCMNodeSuggestion ? this.getEmphasizedTagImage() : this.getDeemphasizedTagImage();
							} else {
								image = this.getGenericTagImage();
							}
						}
						int relevance = isStrictCMNodeSuggestion ? XMLRelevanceConstants.R_STRICTLY_VALID_TAG_NAME : XMLRelevanceConstants.R_TAG_NAME;
						String proposedInfo = getAdditionalInfo(getCMElementDeclaration(parent), elementDecl);
						CustomCompletionProposal proposal = new MarkupCompletionProposal(
								proposedText, contentAssistRequest.getReplacementBeginPosition(),
								contentAssistRequest.getReplacementLength(), cursorAdjustment, image,
								getRequiredName(parent, elementDecl), null, proposedInfo,
								relevance);
						contentAssistRequest.addProposal(proposal);
					}
				}
			}
			if (contentAssistRequest.getProposals().size() == 0) {
				if (error != null) {
					setErrorMessage(error);
				}
				else if ((contentAssistRequest.getMatchString() != null) && (contentAssistRequest.getMatchString().length() > 0)) {
					setErrorMessage(NLS.bind(
							XMLUIMessages.No_known_child_tag_names,
							(new Object[]{parent.getNodeName(), contentAssistRequest.getMatchString()})));
				}
				else {
					setErrorMessage(NLS.bind(
							XMLUIMessages.__Has_no_known_child,
							(new Object[]{parent.getNodeName()})));
				}
			}
		}
		else if (parent.getNodeType() == Node.DOCUMENT_NODE) {
			List childElements = getAvailableRootChildren((Document) parent, childPosition);
			if ( childElements.size() == 0) {
				//No doctype available , treat it as empty document
				addEmptyDocumentProposals(contentAssistRequest, context);
			}
			for (int i = 0; i < childElements.size(); i++) {
				CMNode ed = (CMNode) childElements.get(i);
				if (ed == null) {
					continue;
				}
				String proposedText = null;
				int cursorAdjustment = 0;
				if (ed instanceof CMElementDeclaration) {
					// proposedText = getRequiredName(parent, ed);
					StringBuffer sb = new StringBuffer();
					getContentGenerator().generateTag(parent, (CMElementDeclaration) ed, sb);
					// tag starts w/ '<', but we want to compare to name
					proposedText = sb.toString().substring(1);

					if (!beginsWith(proposedText, matchString)) {
						continue;
					}

					cursorAdjustment = getCursorPositionForProposedText(proposedText);

					String proposedInfo = getAdditionalInfo(null, ed);
					Image image = CMImageUtil.getImage(ed);
					if (image == null) {
						image = this.getGenericTagImage();
					}
					CustomCompletionProposal proposal = new MarkupCompletionProposal(
							proposedText, contentAssistRequest.getReplacementBeginPosition(),
							contentAssistRequest.getReplacementLength(), cursorAdjustment, image,
							getRequiredName(parent, ed), null, proposedInfo, XMLRelevanceConstants.R_TAG_NAME);
					contentAssistRequest.addProposal(proposal);
				}
			}
		}
	}
	
	protected void addEmptyDocumentProposals(
			ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context) {
		
		//by default do nothing
	}
	
	/**
	 * <p>Implementers are allowed to override</p>
	 * 
	 * @return the proposal image to display for generic tag proposals
	 */
	protected Image getGenericTagImage() {
		return XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_TAG_GENERIC);
	}
	
	/**
	 * <p>Implementers are allowed to override</p>
	 * 
	 * @return the proposal image to display for emphasized tag proposals
	 */
	protected Image getEmphasizedTagImage() {
		return XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_TAG_GENERIC_EMPHASIZED);
	}
	
	/**
	 * <p>Implementers are allowed to override</p>
	 * 
	 * @return the proposal image to display for de-emphasized tag proposals
	 */
	protected Image getDeemphasizedTagImage() {
		return XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_TAG_GENERIC_DEEMPHASIZED);
	}
	
	/**
	 * <p>Implementers are allowed to override</p>
	 * 
	 * @return the proposal image to display for entity reference proposals
	 */
	protected Image getEntityReferenceImage() {
		return XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_ENTITY_REFERENCE);
	}
	
	/**
	 * <p>Implementers are allowed to override</p>
	 * 
	 * @return the proposal image to display for not required attributes
	 */
	protected Image getNotRequiredAttributeImage() {
		return XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_ATTRIBUTE);
	}
	
	/**
	 * <p>Implementers are allowed to override</p>
	 * 
	 * @return the proposal image to display for required attributes
	 */
	protected Image getRequiredAttributeImage() {
		return XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_ATT_REQ_OBJ);
	}
	
	/**
	 * This method can check if the cursor is after the XMLPI
	 * 
	 * @param car
	 */
	protected boolean isCursorAfterXMLPI(ContentAssistRequest car) {
		Node aNode = car.getNode();
		boolean xmlpiFound = false;
		Document parent = aNode.getOwnerDocument();
		int xmlpiNodePosition = -1;
		boolean isAfterXMLPI = false;

		if (parent == null) {
			return true; // blank document case
		}

		for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
			boolean xmlpi = ((child.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) && child.getNodeName().equals("xml")); //$NON-NLS-1$
			xmlpiFound = xmlpiFound || xmlpi;
			if (xmlpiFound) {
				if (child instanceof IDOMNode) {
					xmlpiNodePosition = ((IDOMNode) child).getEndOffset();
					isAfterXMLPI = (car.getReplacementBeginPosition() >= xmlpiNodePosition);
				}
				break;
			}
		}
		return isAfterXMLPI;
	}
	
	protected String getRequiredName(Node parentOrOwner, CMNode cmnode) {
		if ((cmnode == null) || (parentOrOwner == null)) {
			if (Debug.displayWarnings) {
				new IllegalArgumentException("Null declaration!").printStackTrace(); //$NON-NLS-1$
			}
			return ""; //$NON-NLS-1$
		}
		return getContentGenerator().getRequiredName(parentOrOwner, cmnode);
	}
	
	private String getRequiredText(Node parentOrOwner, CMAttributeDeclaration attrDecl) {
		if (attrDecl == null) {
			if (Debug.displayWarnings) {
				new IllegalArgumentException("Null attribute declaration!").printStackTrace(); //$NON-NLS-1$
			}
			return ""; //$NON-NLS-1$
		}
		StringBuffer buff = new StringBuffer();
		getContentGenerator().generateRequiredAttribute(parentOrOwner, attrDecl, buff);
		return buff.toString();
	}
	
	protected String getRequiredText(Node parentOrOwner, CMElementDeclaration elementDecl) {
		if (elementDecl == null) {
			if (Debug.displayWarnings) {
				new IllegalArgumentException("Null attribute declaration!").printStackTrace(); //$NON-NLS-1$
			}
			return ""; //$NON-NLS-1$
		}
		StringBuffer buff = new StringBuffer();
		getContentGenerator().generateTag(parentOrOwner, elementDecl, buff);
		return buff.toString();
	}
	
	/**
	 * Retrieves all of the possible valid values for this attribute
	 * declaration
	 */
	private List getPossibleDataTypeValues(Node node, CMAttributeDeclaration ad) {
		List list = null;
		if (node.getNodeType() == Node.ELEMENT_NODE) {
			Element element = (Element) node;
			String[] dataTypeValues = null;
			// The ModelQuery may not be available if the corresponding
			// adapter
			// is absent
			ModelQuery modelQuery = ModelQueryUtil.getModelQuery(element.getOwnerDocument());
			if (modelQuery != null) {
				dataTypeValues = modelQuery.getPossibleDataTypeValues(element, ad);
			}
			else {
				if (ad.getAttrType() != null) {
					dataTypeValues = ad.getAttrType().getEnumeratedValues();
				}
			}
			if (dataTypeValues != null) {
				list = new ArrayList(dataTypeValues.length);
				for (int i = 0; i < dataTypeValues.length; i++) {
					list.add(dataTypeValues[i]);
				}
			}
		}
		if (list == null) {
			list = new ArrayList(0);
		}
		return list;
	}
	
	/**
	 * This is to determine if a tag is a special meta-info comment tag that
	 * shows up as an ELEMENT
	 * 
	 * @param node
	 * @return
	 */
	private boolean isCommentNode(IDOMNode node) {
		return ((node != null) && (node instanceof IDOMElement) && ((IDOMElement) node).isCommentTag());
	}
	
	private boolean isStartTag(IStructuredDocumentRegion sdRegion) {
		boolean result = false;
		if (sdRegion.getRegions().size() > 0) {
			ITextRegion r = sdRegion.getRegions().get(0);
			result = (r.getType() == DOMRegionContext.XML_TAG_OPEN) && sdRegion.isEnded();
		}
		return result;
	}
	
	/**
	 * Gets the corresponding XMLNode, and checks if it's closed.
	 * 
	 * @param startTag
	 * 
	 */
	private boolean needsEndTag(IStructuredDocumentRegion startTag,
			CompletionProposalInvocationContext context) {
		
		boolean result = false;
		IStructuredModel sModel =
			StructuredModelManager.getModelManager().getExistingModelForRead(context.getDocument());
		try {
			if (sModel != null) {
				IDOMNode xmlNode = (IDOMNode) sModel.getIndexedRegion(startTag.getStart());
				if (!isStartTag(startTag)) {
					result = false;
				}
				else if (isSelfClosed(startTag)) {
					result = false;
				}
				else if (!xmlNode.isContainer()) {
					result = false;
				}
				else {
					result = xmlNode.getEndStructuredDocumentRegion() == null;
				}
			}
		}
		finally {
			if (sModel != null) {
				sModel.releaseFromRead();
			}
		}
		return result;
	}
	
	private boolean isSelfClosed(IStructuredDocumentRegion startTag) {
		ITextRegionList regions = startTag.getRegions();
		return regions.get(regions.size() - 1).getType() == DOMRegionContext.XML_EMPTY_TAG_CLOSE;
	}
	
	private String getTagName(IStructuredDocumentRegion sdRegion) {
		ITextRegionList regions = sdRegion.getRegions();
		ITextRegion region = null;
		String name = ""; //$NON-NLS-1$
		for (int i = 0; i < regions.size(); i++) {
			region = regions.get(i);
			if (region.getType() == DOMRegionContext.XML_TAG_NAME) {
				name = sdRegion.getText(region);
				break;
			}
		}
		return name;
	}
	
	/**
	 * return all possible EntityReferenceProposals (according to current
	 * position in doc)
	 */
	private ICompletionProposal[] computeEntityReferenceProposals(ITextRegion completionRegion,
			IDOMNode treeNode,CompletionProposalInvocationContext context) {
		
		// only handle XML content for now
		int documentPosition = context.getInvocationOffset();
		Vector proposals = new Vector(); // ICompletionProposals
		IStructuredDocumentRegion sdRegion =
			ContentAssistUtils.getStructuredDocumentRegion(context.getViewer(), context.getInvocationOffset());
		if ((completionRegion != null) && (completionRegion.getType() == DOMRegionContext.XML_CONTENT)) {
			int nodeOffset = documentPosition - sdRegion.getStartOffset(completionRegion);
			String regionText = sdRegion.getFullText(completionRegion);

			// if directly to the right of a &, region will be null, need to
			// move to
			// the previous region...there might be a better way to do this
			if ((regionText != null) && regionText.trim().equals("") && (documentPosition > 0)) { //$NON-NLS-1$
				IStructuredDocumentRegion prev = treeNode.getStructuredDocument().getRegionAtCharacterOffset(documentPosition - 1);
				if ((prev != null) && prev.getText().equals("&")) { //$NON-NLS-1$
					// https://bugs.eclipse.org/bugs/show_bug.cgi?id=206680
					// examine previous region
					sdRegion = prev;
					completionRegion = prev.getLastRegion();
					regionText = prev.getFullText();
					nodeOffset = 1;
				}
			}

			// string must start w/ &
			if ((regionText != null) && regionText.startsWith("&")) { //$NON-NLS-1$						 		
				String key = (nodeOffset > 0) ? regionText.substring(1, nodeOffset) : ""; //$NON-NLS-1$

				// get entity proposals, passing in the appropriate start
				// string
				ModelQuery mq = ModelQueryUtil.getModelQuery(((Node) treeNode).getOwnerDocument());
				if (mq != null) {
					CMDocument xmlDoc = mq.getCorrespondingCMDocument(treeNode);
					CMNamedNodeMap cmmap = null;
					Properties entities = null;
					if (xmlDoc != null) {
						cmmap = xmlDoc.getEntities();
					}
					if (cmmap != null) {
						entities = mapToProperties(cmmap);
					}
					else // 224787 in absence of content model, just use
					// minimal 5 entities
					{
						entities = new Properties();
						entities.put("quot", "\""); //$NON-NLS-1$ //$NON-NLS-2$
						entities.put("apos", "'"); //$NON-NLS-1$ //$NON-NLS-2$
						entities.put("amp", "&"); //$NON-NLS-1$ //$NON-NLS-2$
						entities.put("lt", "<"); //$NON-NLS-1$ //$NON-NLS-2$
						entities.put("gt", ">"); //$NON-NLS-1$ //$NON-NLS-2$	
						entities.put("nbsp", " "); //$NON-NLS-1$ //$NON-NLS-2$									
					}
					addEntityProposals(proposals, entities, key,
							nodeOffset, sdRegion, completionRegion, context);
				}
			}
		}
		return (ICompletionProposal[]) ((proposals.size() > 0) ? proposals.toArray(new ICompletionProposal[proposals.size()]) : null);
	}
	
	/**
	 * Similar to the call in HTMLContentAssistProcessor. Pass in a node, it
	 * tells you if the document is XML type.
	 * 
	 * @param node
	 * 
	 */
	private boolean isXMLNode(Node node) {
		if (node == null) {
			return false;
		}

		Document doc = null;
		doc = (node.getNodeType() != Node.DOCUMENT_NODE) ? node.getOwnerDocument() : ((Document) node);

		return (doc instanceof IDOMDocument) && ((IDOMDocument) doc).isXMLType();
	}
	
	/**
	 * Checks if cursor position is after doctype tag...
	 * 
	 * @param car
	 * 
	 */
	private boolean isCursorAfterDoctype(ContentAssistRequest car) {
		Node aNode = car.getNode();
		Document parent = aNode.getOwnerDocument();
		int xmldoctypeNodePosition = -1;
		boolean isAfterDoctype = true;

		if (parent == null) {
			return true; // blank document case
		}

		for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
			if (child instanceof IDOMNode) {
				if (child.getNodeType() == Node.DOCUMENT_TYPE_NODE) {
					xmldoctypeNodePosition = ((IDOMNode) child).getEndOffset();
					isAfterDoctype = (car.getReplacementBeginPosition() >= xmldoctypeNodePosition);
					break;
				}
			}
		}
		return isAfterDoctype;
	}
	
	/**
	 * returns a list of CMElementDeclarations
	 * 
	 * @param document
	 * @param childIndex
	 * @return
	 */
	private List getAvailableRootChildren(Document document, int childIndex) {
		List list = null;

		// extract the valid 'root' node name from the DocumentType Node
		DocumentType docType = document.getDoctype();
		String rootName = null;
		if (docType != null) {
			rootName = docType.getNodeName();
		}
		if (rootName == null) {
			return new ArrayList(0);
		}

		for (Node child = document.getFirstChild(); child != null; child = child.getNextSibling()) {
			// make sure the "root" Element isn't already present
			// is it required to be an Element?
			if ((child.getNodeType() == Node.ELEMENT_NODE) && child.getNodeName().equalsIgnoreCase(rootName)) {
				// if the node is missing either the start or end tag, don't
				// count it as present
				if ((child instanceof IDOMNode) && ((((IDOMNode) child).getStartStructuredDocumentRegion() == null) || (((IDOMNode) child).getEndStructuredDocumentRegion() == null))) {
					continue;
				}
				if (Debug.displayInfo) {
					System.out.println(rootName + " already present!"); //$NON-NLS-1$
				}
				setErrorMessage(NLS.bind(XMLUIMessages.The_document_element__, (new Object[]{rootName})));
				return new ArrayList(0);
			}
		}

		list = new ArrayList(1);
		ModelQuery modelQuery = ModelQueryUtil.getModelQuery(document);
		if (modelQuery != null) {
			CMDocument cmdoc = modelQuery.getCorrespondingCMDocument(document);
			if (cmdoc != null) {
				if (rootName != null) {
					CMElementDeclaration rootDecl = (CMElementDeclaration) cmdoc.getElements().getNamedItem(rootName);
					if (rootDecl != null) {
						list.add(rootDecl);
					}
					else {
						// supply the given document name anyway, even if it
						// is an error
						list.add(new SimpleCMElementDeclaration(rootName));

						String location = "" + (docType.getPublicId() != null ? docType.getPublicId() + "/" : "") + (docType.getSystemId() != null ? docType.getSystemId() : ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
						if (location.length() > 0) {
							setErrorMessage(NLS.bind(
									XMLUIMessages.No_definition_for_in,
									(new Object[]{rootName, location})));
						}
						else {
							setErrorMessage(NLS.bind(
									XMLUIMessages.No_definition_for,
									(new Object[]{rootName})));
						}
					}
				}
			}
			else {
				String location = "" + (docType.getPublicId() != null ? docType.getPublicId() + "/" : "") + (docType.getSystemId() != null ? docType.getSystemId() : ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
				if (location.length() > 0) {
					setErrorMessage(NLS.bind(
							XMLUIMessages.No_content_model_for,
							(new Object[]{location})));
				}
				else {
					setErrorMessage(XMLUIMessages.No_content_model_found_UI_);
				}
			}
		}

		return list;
	}
	
	/**
	 * This method determines if any of the attributes in the proposed XMLNode
	 * node, are possible values of attributes from possible Elements at this
	 * point in the document according to the Content Model.
	 * 
	 * @param node
	 *            the element with attributes that you would like to test if
	 *            are possible for possible Elements at this point
	 * @param cmnode
	 *            possible element at this point in the document (depending on
	 *            what 'node' is) true if any attributes of 'node' match any
	 *            possible attributes from 'cmnodes' list.
	 */
	private boolean attributeInList(IDOMNode node, Node parent, CMNode cmnode) {
		if ((node == null) || (parent == null) || (cmnode == null)) {
			return false;
		}
		String elementMatchString = node.getNodeName();
		String cmnodeName = getRequiredName(parent, cmnode);// cmnode.getNodeName();
		if (node instanceof Element) {
			NamedNodeMap map = ((Element) node).getAttributes();
			String attrMatchString = ""; //$NON-NLS-1$
			// iterate attribute possibilities for partially started node
			for (int i = 0; (map != null) && (i < map.getLength()); i++) {
				attrMatchString = map.item(i).getNodeName();
				// filter on whatever user typed for element name already
				if (beginsWith(cmnodeName, elementMatchString)) {
					if (cmnode.getNodeType() == CMNode.ELEMENT_DECLARATION) {
						CMNamedNodeMapImpl attributes = new CMNamedNodeMapImpl(((CMElementDeclaration) cmnode).getAttributes());
						this.addModelQueryAttributeDeclarations(
								node,((CMElementDeclaration) cmnode),attributes);
	
						// iterate possible attributes from a cmnode in
						// proposal list
						for (int k = 0; (attributes != null) && (k < attributes.getLength()); k++) {
							// check if name matches
							if (attributes.item(k).getNodeName().equals(attrMatchString)) {
								return true;
							}
						}
					}
				}
			}
		}
		return false;
	}
	
	private Properties mapToProperties(CMNamedNodeMap map) {
		Properties p = new Properties();
		for (int i = 0; i < map.getLength(); i++) {
			CMEntityDeclaration decl = (CMEntityDeclaration) map.item(i);
			p.put(decl.getName(), decl.getValue());
		}
		return p;
	}
	
	/**
	 * <p>Adds model query attribute declaration proposals</p>
	 * 
	 * @param node
	 * @param elementDecl
	 * @param allAttributes
	 */
	private void addModelQueryAttributeDeclarations(IDOMNode node,
			CMElementDeclaration elementDecl, CMNamedNodeMapImpl allAttributes) {
		
		if (node.getNodeType() == Node.ELEMENT_NODE) {
			List nodes = ModelQueryUtil.getModelQuery(node.getOwnerDocument()).getAvailableContent((Element) node, elementDecl, ModelQuery.INCLUDE_ATTRIBUTES);
			nodes = filterAvailableModelQueryCMNodes(nodes);
			for (int k = 0; k < nodes.size(); k++) {
				CMNode cmnode = (CMNode) nodes.get(k);
				if (cmnode.getNodeType() == CMNode.ATTRIBUTE_DECLARATION) {
					allAttributes.put(cmnode);
				}
			}
		}
	}
	
	/**
	 * returns a list of CMNodes that are available within this parent context
	 * Given the grammar shown below and a snippet of XML code (where the '|'
	 * indicated the cursor position)
	 * the list would return all of the element declarations that are
	 * potential child elements of Foo.
	 *
	 * grammar : Foo -> (A, B, C)
	 * snippet : <Foo><A>|
	 * result : {A, B, C}
	 * 
	 * TODO cs... do we need to pass in the 'kindOfAction'? Seems to me we
	 * could assume it's always an insert.
	 *
	 * @param parent
	 * @param childPosition
	 * @param kindOfAction
	 * @return
	 */
	private List getAvailableChildElementDeclarations(Element parent, int childPosition, int kindOfAction) {
		List modelQueryActions = getAvailableModelQueryActionsAtIndex(parent, childPosition, ModelQuery.VALIDITY_NONE);
		Iterator iterator = modelQueryActions.iterator();
		List cmnodes = new Vector();
		while (iterator.hasNext()) {
			ModelQueryAction action = (ModelQueryAction) iterator.next();
			if ((childPosition < 0) || (((action.getStartIndex() <= childPosition) && (childPosition <= action.getEndIndex())) && (action.getKind() == kindOfAction))) {
				CMNode actionCMNode = action.getCMNode();
				if ((actionCMNode != null) && !cmnodes.contains(actionCMNode)) {
					cmnodes.add(actionCMNode);
				}
			}
		}
		return cmnodes;
	}
	
	/**
	 * returns a list of CMNodes that can be validly inserted at this
	 * childPosition
	 * Given the grammar shown below and a snippet of XML code (where the '|'
	 * indicated the cursor position)
	 * the list would return only the element declarations can be inserted
	 * while maintaing validity of the content.
	 *
	 * grammar : Foo -> (A, B, C)
	 * snippet : <Foo><A>|
	 * result : {B}
	 *    
	 * @param parent
	 * @param childPosition
	 * @param kindOfAction
	 * @return
	 */
	private List getValidChildElementDeclarations(Element parent, int childPosition, int kindOfAction) {
		List modelQueryActions = getAvailableModelQueryActionsAtIndex(parent, childPosition, ModelQuery.VALIDITY_STRICT);
		Iterator iterator = modelQueryActions.iterator();
		List cmnodes = new Vector();
		while (iterator.hasNext()) {
			ModelQueryAction action = (ModelQueryAction) iterator.next();
			if ((childPosition < 0) || (((action.getStartIndex() <= childPosition) && (childPosition <= action.getEndIndex())) && (action.getKind() == kindOfAction))) {
				CMNode actionCMNode = action.getCMNode();
				if ((actionCMNode != null) && !cmnodes.contains(actionCMNode)) {
					cmnodes.add(actionCMNode);
				}
			}
		}
		return cmnodes;
	}
	
	/**
	 * returns a list of ModelQueryActions
	 * 
	 * @param parent
	 * @param index
	 * @param validityChecking
	 * @return
	 */
	private List getAvailableModelQueryActionsAtIndex(Element parent, int index, int validityChecking) {
		List list = new ArrayList();
		CMElementDeclaration parentDecl = getCMElementDeclaration(parent);
		if (parentDecl != null) {
			ModelQuery modelQuery = ModelQueryUtil.getModelQuery(parent.getOwnerDocument());
			// taken from ActionManagers
			// int editMode = modelQuery.getEditMode();
			int editMode = ModelQuery.EDIT_MODE_UNCONSTRAINED;
			int ic = (editMode == ModelQuery.EDIT_MODE_CONSTRAINED_STRICT) ? ModelQuery.INCLUDE_CHILD_NODES | ModelQuery.INCLUDE_SEQUENCE_GROUPS : ModelQuery.INCLUDE_CHILD_NODES;
			modelQuery.getInsertActions(parent, parentDecl, index, ic, validityChecking, list);
		}
		
		list = filterAvailableModelQueryActions(list);
		return list;
	}
	
	/**
	 * <p>Filters out any model query actions that are not valid for this
	 * implementation of the model query computer based on the {@link CMNode}
	 * of the action.</p>
	 * 
	 * @param modelQueryActions
	 * @return the filtered list of {@link ModelQueryAction}s
	 */
	private List filterAvailableModelQueryActions(List modelQueryActions) {
		List filtered = new ArrayList(modelQueryActions.size());
	
		Iterator iterator = modelQueryActions.iterator();
		while (iterator.hasNext()) {
			ModelQueryAction action = (ModelQueryAction) iterator.next();
			if(validModelQueryNode(action.getCMNode())) {
				filtered.add(action);
			}
		}
		
		return filtered;
	}
	
	/**
	 * <p>Filters out any model query {@link CMNode}s that are not valid for this
	 * implementation of the model query computer</p>
	 * 
	 * @param modelQueryNodes
	 * @return the filtered list of {@link CMNode}s
	 */
	private List filterAvailableModelQueryCMNodes(List modelQueryNodes) {
		List filtered = new ArrayList(modelQueryNodes.size());
	
		Iterator iterator = modelQueryNodes.iterator();
		while (iterator.hasNext()) {
			CMNode node = (CMNode) iterator.next();
			if(validModelQueryNode(node)) {
				filtered.add(node);
			}
		}
		
		return filtered;
	}
	
	/**
	 * <p>Adds a generic doc type proposal</p>
	 * 
	 * @param contentAssistRequest
	 * @param context
	 */
	private void addDocTypeProposal(
			ContentAssistRequest contentAssistRequest,
			CompletionProposalInvocationContext context) {
		
		// if a DocumentElement exists, use that for the root Element name
		String rootname = "unspecified"; //$NON-NLS-1$
		if (contentAssistRequest.getNode().getOwnerDocument().getDocumentElement() != null) {
			rootname = contentAssistRequest.getNode().getOwnerDocument().getDocumentElement().getNodeName();
		}

		String proposedText = "<!DOCTYPE " + rootname + " PUBLIC \"//UNKNOWN/\" \"unknown.dtd\">"; //$NON-NLS-1$ //$NON-NLS-2$
		ICompletionProposal proposal = new CustomCompletionProposal(
				proposedText, contentAssistRequest.getReplacementBeginPosition(),
				contentAssistRequest.getReplacementLength(), 10,
				XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_DOCTYPE),
				"<!DOCTYPE ... >", //$NON-NLS-1$
				null, null, XMLRelevanceConstants.R_DOCTYPE);
		// TODO provide special documentation on doc type
		contentAssistRequest.addProposal(proposal);
	}
	
	public static CMElementDeclaration getCMElementDeclaration(Node node) {
		CMElementDeclaration result = null;
		if (node.getNodeType() == Node.ELEMENT_NODE) {
			ModelQuery modelQuery = ModelQueryUtil.getModelQuery(node.getOwnerDocument());
			if (modelQuery != null) {
				result = modelQuery.getCMElementDeclaration((Element) node);
			}
		}
		return result;
	}
	
	/**
	 * Retrieves cmnode's documentation to display in the completion
	 * proposal's additional info. If no documentation exists for cmnode, try
	 * displaying parentOrOwner's documentation
	 * 
	 * String any documentation information to display for cmnode.
	 * <code>null</code> if there is nothing to display.
	 */
	public static String getAdditionalInfo(CMNode parentOrOwner, CMNode cmnode) {
		String addlInfo = null;

		if (cmnode == null) {
			if (Debug.displayWarnings) {
				new IllegalArgumentException("Null declaration!").printStackTrace(); //$NON-NLS-1$
			}
			return null;
		}

		addlInfo = infoProvider.getInfo(cmnode);
		if ((addlInfo == null) && (parentOrOwner != null)) {
			addlInfo = infoProvider.getInfo(parentOrOwner);
		}
		return addlInfo;
	}
}
