| /******************************************************************************* |
| * 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.IReconcileAnnotationKey; |
| 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; |
| |
| 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); |
| IReconcileAnnotationKey key = createKey(elementNode.getFirstStructuredDocumentRegion(), IReconcileAnnotationKey.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); |
| IReconcileAnnotationKey key = createKey(elementNode.getFirstStructuredDocumentRegion(), IReconcileAnnotationKey.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); |
| IReconcileAnnotationKey key = createKey(elementNode.getFirstStructuredDocumentRegion(), IReconcileAnnotationKey.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); |
| IReconcileAnnotationKey key = createKey(elementNode.getFirstStructuredDocumentRegion(), IReconcileAnnotationKey.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++; |
| } |
| } |
| } |