blob: 3e0b4bebe9cadc583e4fee2f006715c01266b298 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2004 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
* Jens Lukowski/Innoopract - initial renaming/restructuring
*
*******************************************************************************/
package org.eclipse.wst.xml.ui.reconcile;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.reconciler.IReconcileResult;
import org.eclipse.wst.common.contentmodel.CMAttributeDeclaration;
import org.eclipse.wst.common.contentmodel.CMDocument;
import org.eclipse.wst.common.contentmodel.CMElementDeclaration;
import org.eclipse.wst.common.contentmodel.CMNamedNodeMap;
import org.eclipse.wst.common.contentmodel.CMNode;
import org.eclipse.wst.common.contentmodel.modelquery.CMDocumentManager;
import org.eclipse.wst.common.contentmodel.modelquery.ModelQuery;
import org.eclipse.wst.common.contentmodel.util.CMDocumentCache;
import org.eclipse.wst.common.contentmodel.util.CMDocumentCacheListener;
import org.eclipse.wst.sse.core.INodeNotifier;
import org.eclipse.wst.sse.core.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.text.ITextRegion;
import org.eclipse.wst.sse.ui.IReleasable;
import org.eclipse.wst.sse.ui.StructuredTextReconciler;
import org.eclipse.wst.sse.ui.internal.SSEUIPlugin;
import org.eclipse.wst.sse.ui.internal.reconcile.ReconcileAnnotationKey;
import org.eclipse.wst.sse.ui.internal.reconcile.TemporaryAnnotation;
import org.eclipse.wst.xml.core.document.XMLAttr;
import org.eclipse.wst.xml.core.document.XMLElement;
import org.eclipse.wst.xml.core.document.XMLNode;
import org.eclipse.wst.xml.core.modelquery.ModelQueryUtil;
import org.eclipse.wst.xml.core.parser.XMLRegionContext;
import org.eclipse.wst.xml.ui.internal.Logger;
import org.eclipse.wst.xml.ui.internal.correction.ProblemIDsXML;
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;
/**
* @deprecated moving toward reconcileValidator extension point
*/
public class ReconcileStepAdapterForXML extends AbstractReconcileStepAdapter implements CMDocumentCacheListener, IReleasable {
/**
* Record of notification sent to this adapter. Will be queued up so
* they're not dealt with until reconciling is actually called from the
* reconciler thread.
*/
public class NotificationEvent {
public Object changedFeature;
public int eventType;
public Object newValue;
public INodeNotifier notifier;
public Object oldValue;
public int pos;
public NotificationEvent(INodeNotifier notifier, int eventType, Object changedFeature, Object oldValue, Object newValue, int pos) {
this.notifier = notifier;
this.eventType = eventType;
this.changedFeature = changedFeature;
this.oldValue = oldValue;
this.newValue = newValue;
this.pos = pos;
}
// used (to see if notifications vector "contains()")
// so we don't queue up "duplicate" events,
// (indicates same eventType, notifier, and changedFeature)
public boolean equals(Object o) {
boolean result = false;
if (o instanceof NotificationEvent) {
NotificationEvent e2 = (NotificationEvent) o;
if (this.notifier == null || e2.notifier == null || this.changedFeature == null || e2.changedFeature == null) {
result = false;
}
result = (this.eventType == e2.eventType && this.notifier == e2.notifier && this.changedFeature == e2.changedFeature);
}
return result;
}
}
protected boolean fCaseSensitive = true;
protected CMDocumentCache fCMDocumentCache;
protected DocumentType fDocumentTypeForRefresh;
protected boolean fNeedsRefreshAll = false;
// required for thread safety
protected List fNotifications = new Vector();
// these are used in conjunction w/ cacheUpdated() notification
// in order to refresh the whole document
protected IProgressMonitor fProgressMonitorForRefresh;
// counter used for repeated reconcile opreations
// to yield the thread control to the next thread
// to improve workbench performance
protected short fReconcileCount = 0;
// will not attempt to validate attribute names starting with the
// following:
protected String[] ignoreAttributeNamesStartingWith = new String[]{"xmlns", "xsi:", "xml:"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
// changing these elements may have an impact on the current content model
// (which suggests to mark everything dirty)
protected String[] mayImpactContentModel = new String[]{"DOCTYPE", "xmlns", "xsi", "xmlns:xsi", "xmlns:xsl", "xsi:schemaLocation", "taglib"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
protected String SEVERITY_MISSING_REQUIRED_ATTR = TemporaryAnnotation.ANNOT_WARNING;
// severities for the problems discoverable by this reconciler; possibly
// user configurable later
protected String SEVERITY_STRUCTURE = TemporaryAnnotation.ANNOT_ERROR;
protected String SEVERITY_UNKNOWN_ATTR = TemporaryAnnotation.ANNOT_ERROR;
protected String SEVERITY_UNKNOWN_ELEMENT = TemporaryAnnotation.ANNOT_ERROR;
public ReconcileStepAdapterForXML() {
super();
}
public void cacheCleared(CMDocumentCache arg0) {
// do nothing
}
public void cacheUpdated(CMDocumentCache arg0, String arg1, int arg2, int arg3, CMDocument arg4) {
// revalidate all
if (Logger.isTracing(StructuredTextReconciler.TRACE_FILTER)) {
String message = "[trace reconciler] > \r\n====================" + "\n cache updated:" + "\n arg0 :" + arg0 + "\n arg1 :" + arg1 + "\n arg3 :" + arg3 + "\n arg4 :" + arg4; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
Logger.trace(StructuredTextReconciler.TRACE_FILTER, message);
}
if (arg3 == CMDocumentCache.STATUS_LOADED) {
Logger.trace(StructuredTextReconciler.TRACE_FILTER, "CMDocument finished loading :" + arg1); //$NON-NLS-1$
doRefreshAll((INodeNotifier) fDocumentTypeForRefresh, fProgressMonitorForRefresh);
}
}
protected IReconcileResult[] doRefreshAll(INodeNotifier notifier, IProgressMonitor monitor) {
Logger.trace(StructuredTextReconciler.TRACE_FILTER, "[trace reconciler] > refreshing all"); //$NON-NLS-1$
synchronized (fDirtyElements) {
fDirtyElements.clear();
}
Document doc = (((Node) notifier).getNodeType() != Node.DOCUMENT_NODE) ? ((Node) notifier).getOwnerDocument() : (Document) notifier;
return reconcileSubtree((INodeNotifier) doc, monitor);
}
protected ModelQuery getModelQuery(Node node) {
return (node.getNodeType() == Node.DOCUMENT_NODE) ? ModelQueryUtil.getModelQuery((Document) node) : ModelQueryUtil.getModelQuery(node.getOwnerDocument());
}
/**
* returns a list of required CMAttributeDeclarations for the given
* element.
*
* @param elementDecl
*
*/
protected List getRequiredAttributes(CMElementDeclaration elementDecl) {
CMNamedNodeMap attrMap = elementDecl.getAttributes();
Iterator it = attrMap.iterator();
CMAttributeDeclaration attr = null;
List result = new ArrayList();
while (it.hasNext()) {
attr = (CMAttributeDeclaration) it.next();
if (attr.getUsage() == CMAttributeDeclaration.REQUIRED) {
result.add(attr);
}
}
return result;
}
/**
* Determine if this Document is an XML/XHTML Document and whether to be
* case sensitive
*/
protected boolean isCaseSensitive(Node node) {
return true;
}
// CMVC 254838
/**
* Indicates if the element is not in the ContentModel (if it's not in the
* ContentModel, but its parent is)
*
* @param element
* @param modelQuery
* @return whether or not the element is unknown according to its
* associated ContentModel.
*/
protected boolean isUnknown(Element element, ModelQuery modelQuery) {
boolean result = false;
CMElementDeclaration ed = modelQuery.getCMElementDeclaration(element);
if (ed == null) {
// make sure parent declaration exists, and is not inferred
Node parentNode = element.getParentNode();
if (parentNode != null && parentNode.getNodeType() == Node.ELEMENT_NODE) {
CMElementDeclaration parentEd = modelQuery.getCMElementDeclaration((Element) parentNode);
// 2/19/04 porting iFix for lax schema suppport
result = (parentEd != null) && !Boolean.TRUE.equals(parentEd.getProperty("isInferred")) //$NON-NLS-1$
&& !Boolean.TRUE.equals(parentEd.getProperty("isLax")); //$NON-NLS-1$
}
// need one error for the root at least
// to indicate the document is wrong...
Document ownerDoc = element.getOwnerDocument();
if (ownerDoc != null && ownerDoc.getDocumentElement() == element) {
CMDocument cmDoc = modelQuery.getCorrespondingCMDocument(ownerDoc);
result = (cmDoc != null && cmDoc.getElements().getLength() > 0);
}
} else {
if (ed.getProperty("isInferred") != null && Boolean.TRUE.equals(ed.getProperty("isInferred")) //$NON-NLS-1$ //$NON-NLS-2$
|| (ed.getProperty("partialContentModel") != null && Boolean.TRUE.equals(ed.getProperty("partialContentModel")))) { //$NON-NLS-1$ //$NON-NLS-2$
result = false;
}
}
return result;
}
/**
* Checks if name matches any mayImpactContentModel[] strings
*
* @param name
* @return if a match is found, return true, else return false
*/
private boolean mayAffectContentModel(String name) {
// TODO (pa) may need to be smarter if the attribute name is broken...
StringTokenizer st = new StringTokenizer(name, ":", false); //$NON-NLS-1$
String prefix = ""; //$NON-NLS-1$
if (st.hasMoreTokens())
prefix = st.nextToken();
for (int i = 0; i < mayImpactContentModel.length; i++) {
if (mayImpactContentModel[i].indexOf(name) != -1 || mayImpactContentModel[i].startsWith(prefix))
return true;
}
return false;
}
public void notifyChanged(INodeNotifier notifier, int eventType, Object changedFeature, Object oldValue, Object newValue, int pos) {
synchronized (fNotifications) {
NotificationEvent newEvent = new NotificationEvent(notifier, eventType, changedFeature, oldValue, newValue, pos);
if (!fNotifications.contains(newEvent))
fNotifications.add(newEvent);
}
}
public void processNotification(INodeNotifier notifier, int eventType, Object changedFeature, Object oldValue, Object newValue, int pos, IProgressMonitor monitor) {
if (isCanceled(monitor))
return;
// (nsd) pa_TODO: we need to mark more or widen the scope affected by
// the next reconcile() call
// TODO: Handle multi-Node changes from Doctype Declarations, Taglib
// directives,
// and schema and namespace related attributes (DOCTYPE, taglib,
// xmlns,
// xsi...)
// ** we currently don't get a notify on changed taglib...
// we're going to validate everything again anyways after
// proccessNotifications() has completed (in reconcile():
// processNotifications() > refreshAll()),
// no sense to do it here if we refreshingAll
if (fNeedsRefreshAll)
return;
if (eventType == INodeNotifier.CHANGE || eventType == INodeNotifier.REMOVE) {
if (changedFeature instanceof Node && ((Node) changedFeature).getNodeType() == Node.ATTRIBUTE_NODE) {
if (mayAffectContentModel(((Node) changedFeature).getNodeName())) {
fNeedsRefreshAll = true;
}
} else if (changedFeature instanceof Node && ((Node) changedFeature).getNodeType() == Node.DOCUMENT_TYPE_NODE) {
fNeedsRefreshAll = true;
} else if (notifier instanceof Node && ((Node) notifier).getNodeType() == Node.DOCUMENT_TYPE_NODE) {
fNeedsRefreshAll = true;
} else {
// pa_TODO need to handle taglib definition changes...
// if(mayAffectContentModel(((Node)changedFeature).getNodeName()))
// {
// System.out.println("dunno what changed > " +
// changedFeature);
// fNeedsRefreshAll = true;
// }
}
if (isCanceled(monitor))
return;
fNeedsRefreshAll = true;
}
if (eventType == INodeNotifier.CHANGE && changedFeature instanceof Element) {
markForReconciling(changedFeature);
} else if (eventType == INodeNotifier.ADD && newValue instanceof Node) {
Node newNode = (Node) newValue;
if (newNode.getNodeType() == Node.DOCUMENT_TYPE_NODE || newNode.getNodeName().equals("jsp:directive.taglib")) { // $NON-NLS-1$
// //$NON-NLS-1$
fNeedsRefreshAll = true;
} else {
markForReconciling(newNode);
}
}
markForReconciling(notifier);
}
protected void processNotifications(IProgressMonitor monitor) {
fProgressMonitorForRefresh = monitor;
NotificationEvent[] events = null;
synchronized (fNotifications) {
if (fNotifications.isEmpty()) {
return;
}
events = (NotificationEvent[]) fNotifications.toArray(new NotificationEvent[0]);
fNotifications.clear();
}
for (int i = 0; i < events.length; i++) {
processNotification(events[i].notifier, events[i].eventType, events[i].changedFeature, events[i].oldValue, events[i].newValue, events[i].pos, monitor);
}
}
public IReconcileResult[] reconcile(IProgressMonitor monitor, XMLNode xmlNode) {
processNotifications(monitor);
IReconcileResult[] results = EMPTY_RECONCILE_RESULT_SET;
if (fNeedsRefreshAll) {
results = doRefreshAll(xmlNode, monitor);
fNeedsRefreshAll = false;
} else {
results = super.reconcile(monitor, xmlNode);
}
return results;
}
/**
* Called by super.reconcile(IAnnotationModel) on each Notifier
*
*/
protected IReconcileResult[] reconcile(Object o, IProgressMonitor monitor) {
super.reconcile(o, monitor);
ModelQuery mq = null;
if (o instanceof XMLNode) {
XMLNode xmlNode = (XMLNode) o;
mq = getModelQuery(xmlNode);
if (mq != null) {
fCaseSensitive = isCaseSensitive(xmlNode);
return validate(mq, xmlNode);
}
}
// if we are in a large reconciling loop (like when reconciling the
// entire doc), this ensures
// that other Threads have a chance to run.
yieldIfNeeded();
return EMPTY_RECONCILE_RESULT_SET;
}
/**
* Reconcile the Node and all children of the Notifier passed in.
*
* @param notifier
* @param monitor
*/
protected IReconcileResult[] reconcileSubtree(INodeNotifier notifier, IProgressMonitor monitor) {
IReconcileResult[] temp = EMPTY_RECONCILE_RESULT_SET;
List results = new ArrayList();
if (!isCanceled(monitor)) {
if (notifier != null && notifier instanceof XMLNode) {
XMLNode current = (XMLNode) notifier;
// loop siblings
while (current != null) {
// mark whatever type nodes we wanna make dirty
if (current.getNodeType() == Node.ELEMENT_NODE || current.getNodeType() == Node.DOCUMENT_TYPE_NODE || current.getNodeType() == Node.DOCUMENT_NODE || current.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
temp = reconcile(current, monitor);
for (int i = 0; i < temp.length; i++)
results.add(temp[i]);
}
// drop one level deeper if necessary
if (current.getFirstChild() != null) {
temp = reconcileSubtree((INodeNotifier) current.getFirstChild(), monitor);
for (int i = 0; i < temp.length; i++)
results.add(temp[i]);
}
current = (XMLNode) current.getNextSibling();
}
}
temp = new IReconcileResult[results.size()];
System.arraycopy(results.toArray(), 0, temp, 0, results.size());
}
return temp;
}
/**
* Called from the ReconcileAdapterFactory
*
*/
public void release() {
if (fCMDocumentCache != null) {
fCMDocumentCache.removeListener(this);
fCMDocumentCache = null;
}
}
/**
* Determines whether the given Attr should not be validated according to
* the ignoreAttributeNamesStartingWith array
*
* @param attr
*/
protected boolean shouldIgnore(Attr attr) {
boolean result = false;
String name = attr.getNodeName();
for (int i = 0; i < ignoreAttributeNamesStartingWith.length; i++) {
if (fCaseSensitive) {
if (name.startsWith(ignoreAttributeNamesStartingWith[i]))
result = true;
} else {
try {
if (name.length() >= ignoreAttributeNamesStartingWith[i].length() && ignoreAttributeNamesStartingWith[i].equalsIgnoreCase(name.substring(0, ignoreAttributeNamesStartingWith[i].length())))
result = true;
} catch (StringIndexOutOfBoundsException e) {
result = true;
}
}
}
return result;
}
private void updateCMDocumentCache(ModelQuery mq, XMLNode xmlNode) {
if (mq != null) {
CMDocumentManager cmDocManager = mq.getCMDocumentManager();
if (cmDocManager != null) {
CMDocumentCache newCache = cmDocManager.getCMDocumentCache();
if (newCache != null) {
if (fCMDocumentCache == null) {
// create fCMDocCache if necessary
fCMDocumentCache = newCache;
fCMDocumentCache.addListener(this);
fDocumentTypeForRefresh = (DocumentType) xmlNode;
} else if (fCMDocumentCache != newCache) {
fCMDocumentCache.removeListener(this);
fCMDocumentCache = newCache;
fCMDocumentCache.addListener(this);
fDocumentTypeForRefresh = (DocumentType) xmlNode;
}
}
}
}
}
/**
* Called by reconcile(IAnnotationModel, Object) when the Object is a
* Notifier
*
*/
protected IReconcileResult[] validate(ModelQuery mq, XMLNode xmlNode) {
List results = new ArrayList();
if (xmlNode == null || !(xmlNode.getNodeType() == Node.ELEMENT_NODE || xmlNode.getNodeType() == Node.DOCUMENT_TYPE_NODE))
return EMPTY_RECONCILE_RESULT_SET;
// return early if the Node has gone stale
if (xmlNode.getParentNode() == null || xmlNode.getOwnerDocument() == null) {
return EMPTY_RECONCILE_RESULT_SET;
}
if (xmlNode.getNodeType() == Node.DOCUMENT_TYPE_NODE) {
// sets CMDocumentCacheListener (this)
updateCMDocumentCache(mq, xmlNode);
}
CMDocument doc = mq.getCorrespondingCMDocument(xmlNode.getOwnerDocument());
// looks like this is a bad check to do... I thought we took it out
// before
// if(doc == null)
// return EMPTY_RECONCILE_RESULT_SET;
if (doc != null && doc.getElements().getLength() == 0) {
// an empty document
return EMPTY_RECONCILE_RESULT_SET;
}
// continue on null or FALSE; inferred grammars aren't predefined so
// there's no point in continuing
if (doc != null && doc.getProperty("isInferred") != null && Boolean.TRUE.equals(doc.getProperty("isInferred"))) { //$NON-NLS-1$ //$NON-NLS-2$
return EMPTY_RECONCILE_RESULT_SET;
}
// if xmlNode is DOCTYPE, skip to the first element
// if there are no elements, return (don't validate)
XMLNode elementNode = xmlNode;
if (xmlNode.getNodeType() == Node.DOCUMENT_TYPE_NODE) {
boolean elementFound = false;
while ((elementNode = (XMLNode) elementNode.getNextSibling()) != null) {
if (elementNode.getNodeType() == Node.ELEMENT_NODE) {
elementFound = true;
break;
}
}
if (!elementFound)
return EMPTY_RECONCILE_RESULT_SET;
}
XMLElement element = (XMLElement) elementNode;
// boolean needsEndTag = true;
// test for a known element, if it's known, continue validating it
CMElementDeclaration elementDecl = mq.getCMElementDeclaration(element);
if (elementDecl != null) {
// needsEndTag = needsEndTag(elementNode, elementDecl);
NamedNodeMap attrs = element.getAttributes();
List reqAttrList = getRequiredAttributes(elementDecl);
for (int i = 0; i < attrs.getLength(); i++) {
XMLAttr attr = (XMLAttr) attrs.item(i);
if (!shouldIgnore(attr)) {
// iFix V511i
// CMVC 272647, attributes with namespace prefix get
// marked
// as error (even though they aren't)
// CMNode attrDecl =
// elementDecl.getAttributes().getNamedItem(attr.getNodeName());
CMNode attrDecl = elementDecl.getAttributes().getNamedItem(attr.getLocalName());
// test for a known attribute
if (attrDecl != null) {
// test for a known value (if there is an enumerated
// list of them)
String[] values = mq.getPossibleDataTypeValues(element, attrDecl);
String currentValue = attr.getValue();
boolean found = valueMatch(values, currentValue);
if (!found) {
int start = attr.getValueRegionStartOffset();
int length = attr.getValueRegion().getTextLength();
Object[] args = {currentValue.trim()};
String message = SSEUIPlugin.getResourceString("%Invalid_value_{0}", args);
Position p = new Position(start, length);
ReconcileAnnotationKey key = createKey(elementNode.getFirstStructuredDocumentRegion(), ReconcileAnnotationKey.PARTIAL);
results.add(new TemporaryAnnotation(p, SEVERITY_UNKNOWN_ATTR, message, key));
}
// remove from known required attribute list
reqAttrList.remove(attrDecl);
} else {
Object[] args = {attr.getName()};
String message = SSEUIPlugin.getResourceString("%Unknown_attribute_{0}", args);
int start = attr.getNameRegionStartOffset();
int length = attr.getNameRegion().getTextLength();
Position p = new Position(start, length);
ReconcileAnnotationKey key = createKey(elementNode.getFirstStructuredDocumentRegion(), ReconcileAnnotationKey.PARTIAL);
results.add(new TemporaryAnnotation(p, SEVERITY_UNKNOWN_ATTR, message, key, ProblemIDsXML.UnknownAttr));
}
} else {
// remove so we don't flag "ignored" attributes as missing
reqAttrList.remove(elementDecl.getAttributes().getNamedItem(attr.getNodeName()));
}
}
// if there are missing required attributes, create annotations
// for
// them
if (reqAttrList != null && !reqAttrList.isEmpty()) {
Iterator it = reqAttrList.iterator();
int start = 0;
int length = 1;
CMAttributeDeclaration attr = null;
while (it.hasNext()) {
attr = (CMAttributeDeclaration) it.next();
// sometimes getFirstStructuredDocumentRegion can return
// null, this is a safety
start = (element.getFirstStructuredDocumentRegion() != null) ? element.getFirstStructuredDocumentRegion().getStartOffset() : element.getStartOffset();
length = (element.getFirstStructuredDocumentRegion() != null) ? element.getFirstStructuredDocumentRegion().getLength() : 1;
Object[] args = {attr.getAttrName()};
String message = SSEUIPlugin.getResourceString("%Missing_required_attribute_{0}", args);
Position p = new Position(start, length);
ReconcileAnnotationKey key = createKey(elementNode.getFirstStructuredDocumentRegion(), ReconcileAnnotationKey.PARTIAL);
TemporaryAnnotation annotation = new TemporaryAnnotation(p, SEVERITY_MISSING_REQUIRED_ATTR, message, key, ProblemIDsXML.MissingRequiredAttr);
IStructuredDocumentRegion startStructuredDocumentRegion = element.getStartStructuredDocumentRegion();
if (startStructuredDocumentRegion != null) {
String requiredAttrName = attr.getAttrName();
String defaultAttrValue = attr.getDefaultValue();
String insertString;
if (defaultAttrValue == null)
insertString = requiredAttrName + "=\"" + requiredAttrName + "\""; //$NON-NLS-1$ //$NON-NLS-2$
else
insertString = requiredAttrName + "=\"" + defaultAttrValue + "\""; //$NON-NLS-1$ //$NON-NLS-2$
ITextRegion lastRegion = startStructuredDocumentRegion.getLastRegion();
int insertOffset = lastRegion.getEnd();
if (lastRegion.getEnd() == lastRegion.getTextEnd())
insertString = " " + insertString; //$NON-NLS-1$
if (lastRegion.getType() == XMLRegionContext.XML_TAG_CLOSE)
insertOffset = lastRegion.getStart();
Object[] additionalFixInfo = {insertString, new Integer(insertOffset)};
annotation.setAdditionalFixInfo(additionalFixInfo);
results.add(annotation);
}
}
}
} else if (isUnknown(element, mq)) { // CMVC 254838
int start = element.getStartOffset();
int length = element.getEndOffset() - element.getStartOffset();
if (element.getStartStructuredDocumentRegion() != null && element.getStartStructuredDocumentRegion().getNumberOfRegions() > 1) {
ITextRegion name = element.getStartStructuredDocumentRegion().getRegions().get(1);
start = element.getStartStructuredDocumentRegion().getStartOffset(name);
length = name.getTextLength();
}
Object[] args = {element.getNodeName()};
String message = SSEUIPlugin.getResourceString("%Unknown_element_{0}", args);
Position p = new Position(start, length);
ReconcileAnnotationKey key = createKey(elementNode.getFirstStructuredDocumentRegion(), ReconcileAnnotationKey.PARTIAL);
TemporaryAnnotation annotation = new TemporaryAnnotation(p, SEVERITY_UNKNOWN_ELEMENT, message, key, ProblemIDsXML.UnknownElement);
// quick fix info
int startTagOffset = -1, startTagLength = -1, endTagOffset = -1, endTagLength = -1;
if (element.getStartStructuredDocumentRegion() != null) {
startTagOffset = element.getStartStructuredDocumentRegion().getStartOffset();
startTagLength = element.getStartStructuredDocumentRegion().getLength();
}
if (element.getEndStructuredDocumentRegion() != null) {
endTagOffset = element.getEndStructuredDocumentRegion().getStartOffset();
endTagLength = element.getEndStructuredDocumentRegion().getLength();
}
Object[] additionalFixInfo = {new Integer(startTagOffset), new Integer(startTagLength), new Integer(endTagOffset), new Integer(endTagLength)};
annotation.setAdditionalFixInfo(additionalFixInfo);
results.add(annotation);
}
IReconcileResult[] reconcileResults = new IReconcileResult[results.size()];
System.arraycopy(results.toArray(), 0, reconcileResults, 0, results.size());
return reconcileResults;
}
/**
* Determines if String value is within the values array given the current
* case sensitivity
*
* @param values
* @param value
*/
protected boolean valueMatch(String[] values, String value) {
boolean found = (values == null || values.length == 0 || value.length() == 0);
for (int j = 0; j < values.length && !found; j++) {
if (fCaseSensitive) {
if (values[j].equals(value))
found = true;
} else if (values[j].equalsIgnoreCase(value))
found = true;
}
return found;
}
// CMVC 255301
// If we are in a large reconciling loop, this ensures
// that other Threads have a chance to run.
protected void yieldIfNeeded() {
// 100 is arbitrary, may need a better number
if (fReconcileCount >= 100) {
Thread.yield();
fReconcileCount = 0;
} else {
fReconcileCount++;
}
}
}