blob: 2352fefc1ee097f641175749ca4697571ed0c748 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 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();
// check whether an attribute really exists for the replacement
// offsets AND if it possesses a value
boolean attrAtLocationHasValue = false;
NamedNodeMap attrs = node.getAttributes();
for (int i = 0; i < attrs.getLength(); i++) {
AttrImpl existingAttr = (AttrImpl) attrs.item(i);
ITextRegion name = existingAttr.getNameRegion();
if ((sdRegion.getStartOffset(name) <= contentAssistRequest.getReplacementBeginPosition()) &&
(sdRegion.getStartOffset(name) + name.getLength() >= contentAssistRequest.getReplacementBeginPosition() + contentAssistRequest.getReplacementLength()) &&
(existingAttr.getValueRegion() != null)) {
attrAtLocationHasValue = 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) &&
(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 CustomCompletionProposal(
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) {
value = existingAttrNode.getNodeValue();
}
if ((value != null) && (value.length() > 0)) {
proposedText = getRequiredName(node, attrDecl);
}
else {
proposedText = getRequiredText(node, attrDecl);
}
proposal = new CustomCompletionProposal(proposedText,
contentAssistRequest.getReplacementBeginPosition(),
contentAssistRequest.getReplacementLength(),
attrDecl.getNodeName().length() + 2, 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();
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();
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$
CustomCompletionProposal proposal = new CustomCompletionProposal(
rString, rOffset, rLength, possibleValue.length() + 1,
XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_ENUM),
rString, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE);
contentAssistRequest.addProposal(proposal);
}
}
}
if(defaultValue != null && ((matchString.length() == 0) || defaultValue.startsWith(matchString))) {
String rString = "\"" + defaultValue + "\""; //$NON-NLS-1$ //$NON-NLS-2$
CustomCompletionProposal proposal = new CustomCompletionProposal(
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 CustomCompletionProposal(
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 CustomCompletionProposal(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)) {
String rValue = "\"" + currentValue + "\""; //$NON-NLS-2$//$NON-NLS-1$
proposal = new CustomCompletionProposal(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 CustomCompletionProposal(">", //$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);
if ((aXMLNode.getEndStructuredDocumentRegion() == null) && ((ed == null) || (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 CustomCompletionProposal(
replacementText, contentAssistRequest.getStartOffset(),
contentAssistRequest.getRegion().getTextLength(),
replacementText.length(), image, displayText, null,
proposedInfo, XMLRelevanceConstants.R_END_TAG_NAME);
}
else {
proposal = new CustomCompletionProposal(
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 CustomCompletionProposal(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 CustomCompletionProposal(
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 CustomCompletionProposal(">", //$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 CustomCompletionProposal("></" + 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 CustomCompletionProposal("/>", //$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 CustomCompletionProposal(">", //$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 CustomCompletionProposal(
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 CustomCompletionProposal(
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 CustomCompletionProposal(
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 CustomCompletionProposal(
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 (((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 CustomCompletionProposal(
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);
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 CustomCompletionProposal(
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;
}
}