| /******************************************************************************* |
| * Copyright (c) 2006 Oracle Corporation. |
| * 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: |
| * Gerry Kessler/Oracle - initial API and implementation |
| * |
| ********************************************************************************/ |
| package org.eclipse.jst.jsf.validation.internal; |
| |
| import java.io.IOException; |
| import java.text.MessageFormat; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.emf.common.util.Diagnostic; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jst.jsf.common.dom.TagIdentifier; |
| import org.eclipse.jst.jsf.common.internal.types.CompositeType; |
| import org.eclipse.jst.jsf.common.internal.types.TypeComparator; |
| import org.eclipse.jst.jsf.common.metadata.Entity; |
| import org.eclipse.jst.jsf.common.metadata.Trait; |
| import org.eclipse.jst.jsf.common.metadata.internal.MetaDataModelContextImpl; |
| import org.eclipse.jst.jsf.common.metadata.query.IMetaDataModelContext; |
| import org.eclipse.jst.jsf.common.metadata.query.MetaDataQueryHelper; |
| import org.eclipse.jst.jsf.common.sets.AxiomaticSet; |
| import org.eclipse.jst.jsf.common.sets.ConcreteAxiomaticSet; |
| import org.eclipse.jst.jsf.context.resolver.structureddocument.IDOMContextResolver; |
| import org.eclipse.jst.jsf.context.resolver.structureddocument.IStructuredDocumentContextResolverFactory; |
| import org.eclipse.jst.jsf.context.resolver.structureddocument.ITaglibContextResolver; |
| import org.eclipse.jst.jsf.context.structureddocument.IStructuredDocumentContext; |
| import org.eclipse.jst.jsf.context.structureddocument.IStructuredDocumentContextFactory; |
| import org.eclipse.jst.jsf.core.internal.JSFCorePlugin; |
| import org.eclipse.jst.jsf.core.internal.tld.TagIdentifierFactory; |
| import org.eclipse.jst.jsf.core.set.constraint.MemberConstraint; |
| import org.eclipse.jst.jsf.core.set.mapping.ElementToTagIdentifierMapping; |
| import org.eclipse.jst.jsf.core.tagmatcher.EvaluationException; |
| import org.eclipse.jst.jsf.core.tagmatcher.InvalidExpressionException; |
| import org.eclipse.jst.jsf.core.tagmatcher.XPathMatchingAlgorithm; |
| import org.eclipse.jst.jsf.metadataprocessors.MetaDataEnabledProcessingFactory; |
| import org.eclipse.jst.jsf.metadataprocessors.features.ELIsNotValidException; |
| import org.eclipse.jst.jsf.metadataprocessors.features.IValidELValues; |
| import org.eclipse.jst.jsf.metadataprocessors.features.IValidValues; |
| import org.eclipse.jst.jsf.metadataprocessors.features.IValidationMessage; |
| import org.eclipse.jst.jsf.validation.internal.constraints.ContainsTagConstraint; |
| import org.eclipse.jst.jsf.validation.internal.constraints.TagId; |
| import org.eclipse.jst.jsf.validation.internal.constraints.TagSet; |
| import org.eclipse.jst.jsf.validation.internal.el.ELExpressionValidator; |
| import org.eclipse.jst.jsf.validation.internal.el.diagnostics.DiagnosticFactory; |
| import org.eclipse.jst.jsf.validation.internal.el.diagnostics.ValidationMessageFactory; |
| import org.eclipse.jst.jsp.core.internal.domdocument.DOMModelForJSP; |
| import org.eclipse.jst.jsp.core.internal.regions.DOMJSPRegionContexts; |
| import org.eclipse.jst.jsp.core.internal.validation.JSPValidator; |
| 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.ITextRegionCollection; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList; |
| import org.eclipse.wst.sse.ui.internal.reconcile.validator.ISourceValidator; |
| import org.eclipse.wst.validation.internal.provisional.core.IMessage; |
| import org.eclipse.wst.validation.internal.provisional.core.IReporter; |
| import org.eclipse.wst.validation.internal.provisional.core.IValidationContext; |
| import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| |
| /** |
| * A JSP page validator that makes use of the JSF metadata processing framework so that JSP page |
| * semantics can be validated. |
| * |
| * This implementation currently only validates attribute values. |
| * @author Gerry Kessler - Oracle |
| */ |
| public class JSPSemanticsValidator extends JSPValidator implements ISourceValidator{ |
| // TODO: should the source validator be a separate class in jsp.ui? |
| // problem with simple split off is that preference must also be split off |
| static final boolean DEBUG; |
| static { |
| String value = Platform.getDebugOption("org.eclipse.jst.jsf.validation.internal.el/debug/jspsemanticsvalidator"); //$NON-NLS-1$ |
| DEBUG = value != null && value.equalsIgnoreCase("true"); //$NON-NLS-1$ |
| } |
| private final static ElementToTagIdentifierMapping elem2TagIdMapper = new ElementToTagIdentifierMapping(); |
| private IDocument fDocument; |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jst.jsp.core.internal.validation.JSPValidator#validateFile(org.eclipse.core.resources.IFile, org.eclipse.wst.validation.internal.provisional.core.IReporter) |
| */ |
| protected void validateFile(IFile file, IReporter reporter) { |
| IStructuredModel model = null; |
| if (DEBUG) |
| System.out.println("executing JSPSemanticsValidator.validateFile"); |
| try { |
| model = StructuredModelManager.getModelManager().getModelForRead(file); |
| |
| if (model instanceof DOMModelForJSP) |
| { |
| DOMModelForJSP jspModel = (DOMModelForJSP) model; |
| IStructuredDocument structuredDoc = jspModel.getStructuredDocument(); |
| IStructuredDocumentRegion curNode = structuredDoc.getFirstStructuredDocumentRegion(); |
| while (null != curNode && !reporter.isCancelled()) { |
| if (curNode.getFirstRegion().getType() == DOMRegionContext.XML_TAG_OPEN ) |
| { |
| validateTag(curNode, reporter, file, false); |
| } |
| curNode = curNode.getNext(); |
| } |
| } |
| } |
| catch (CoreException e) |
| { |
| JSFCorePlugin.log("Error validating JSF", e); |
| } |
| catch (IOException e) |
| { |
| JSFCorePlugin.log("Error validating JSF", e); |
| } |
| finally |
| { |
| if (null != model) |
| model.releaseFromRead(); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.wst.sse.ui.internal.reconcile.validator.ISourceValidator#validate(org.eclipse.jface.text.IRegion, org.eclipse.wst.validation.internal.provisional.core.IValidationContext, org.eclipse.wst.validation.internal.provisional.core.IReporter) |
| */ |
| public void validate(IRegion dirtyRegion, IValidationContext helper, IReporter reporter) { |
| if (DEBUG) |
| System.out.println("exec JSPSemanticsValidator.validateRegion"); |
| |
| if (fDocument instanceof IStructuredDocument) { |
| IStructuredDocument sDoc = (IStructuredDocument) fDocument; |
| IStructuredDocumentRegion[] regions = sDoc.getStructuredDocumentRegions(dirtyRegion.getOffset(), dirtyRegion.getLength()); |
| if (regions != null){ |
| validateTag(regions[0], reporter, getFile(helper), true); |
| } |
| } |
| } |
| |
| private IFile getFile(IValidationContext helper) { |
| String[] uris = helper.getURIs(); |
| IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); |
| if (uris.length > 0) |
| return wsRoot.getFile(new Path(uris[0])); |
| |
| return null; |
| } |
| |
| |
| /** |
| * Validates a JSP tag. |
| * |
| * Currently only attribute values with supplied annotation meta-data is being validated. |
| * Also, only JSF EL is being validated and not JSP EL. |
| * |
| * This method may be extended in the future to validate tag semantics an other cross attribute |
| * validations. |
| * |
| * @param container |
| * @param reporter |
| * @param file |
| * @param isIncremental -- true if this validation is "as you type" |
| * |
| */ |
| private void validateTag(ITextRegionCollection container, IReporter reporter, IFile file, boolean isIncremental) { |
| ITextRegionCollection containerRegion = container; |
| Iterator regions = containerRegion.getRegions().iterator(); |
| ITextRegion region = null; |
| String uri = null; |
| String tagName = null; |
| String attrName = null; |
| while (regions.hasNext() && !reporter.isCancelled()) { |
| region = (ITextRegion) regions.next(); |
| String type = region.getType(); |
| IDOMContextResolver resolver = null; |
| ITaglibContextResolver tagLibResolver = null; |
| if (type != null && (type == DOMRegionContext.XML_TAG_NAME || type == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME || type == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE)){ |
| IStructuredDocumentContext context = IStructuredDocumentContextFactory.INSTANCE.getContext(((IStructuredDocumentRegion)containerRegion).getParentDocument(), containerRegion.getStartOffset() + region.getStart()); |
| resolver = IStructuredDocumentContextResolverFactory.INSTANCE.getDOMContextResolver(context); |
| if (type == DOMRegionContext.XML_TAG_NAME) { |
| tagLibResolver = IStructuredDocumentContextResolverFactory.INSTANCE.getTaglibContextResolver(context); |
| Node node = resolver.getNode(); |
| tagName = resolver.getNode().getLocalName(); |
| uri = tagLibResolver.getTagURIForNodeName(resolver.getNode()); |
| |
| if (node instanceof Element && uri != null) |
| { |
| validateContainment((Element)node, uri, tagName, reporter, file, context); |
| } |
| |
| if (DEBUG) |
| System.out.println(addDebugSpacer(1)+"tagName= "+ (tagName!= null ? tagName : "null") +": uri= "+(uri != null ? uri : "null") ); |
| } |
| else if (type == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME){ |
| attrName = resolver.getNode().getNodeName(); |
| if (DEBUG) |
| System.out.println(addDebugSpacer(2)+"attrName= "+(attrName != null ? attrName : "null" )); |
| if (uri != null && tagName != null) |
| { |
| // TODO: validateAttribute(context, region, uri, resolver.getNode(), file); |
| } |
| } |
| else if (type == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE){ |
| final String attributeVal = |
| resolver.getNode().getNodeValue(); |
| |
| // if there's elText then validate it |
| // TODO: this approach will fail with mixed expressions |
| if (!checkIfELAndValidate(region, |
| context, |
| uri, |
| tagName, |
| attrName, |
| attributeVal, |
| isIncremental, |
| reporter, |
| file) |
| ) |
| { |
| // else validate as static attribute value |
| if (DEBUG) |
| System.out.println(addDebugSpacer(3)+"attrVal= "+(attributeVal != null ? attributeVal : "null") ); |
| |
| if (uri != null && tagName != null && attrName != null) |
| validateAttributeValue(context, uri, tagName, attrName, attributeVal, reporter, file); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Checks the region to see if it contains an EL attribute value. If it |
| * does, validates it |
| * @return true if validated EL, false otherwise |
| */ |
| private boolean checkIfELAndValidate(ITextRegion region, |
| IStructuredDocumentContext context, |
| String uri, |
| String tagName, |
| String attrName, |
| String attrValue, |
| boolean isIncremental, |
| IReporter reporter, |
| IFile file) |
| { |
| if (region instanceof ITextRegionCollection) { |
| ITextRegionCollection parentRegion = ((ITextRegionCollection) region); |
| if (parentRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) |
| { |
| // look for attribute pattern "#{}" |
| // TODO: need to generalize this for RValue concatenation |
| final ITextRegionList regionList = parentRegion.getRegions(); |
| if (regionList.size() >= 4) |
| { |
| ITextRegion openQuote = regionList.get(0); |
| ITextRegion openVBLQuote = regionList.get(1); |
| |
| if ( (openQuote.getType() == DOMJSPRegionContexts.XML_TAG_ATTRIBUTE_VALUE_DQUOTE |
| || openQuote.getType() == DOMJSPRegionContexts.XML_TAG_ATTRIBUTE_VALUE_SQUOTE) |
| && (openVBLQuote.getType() == DOMJSPRegionContexts.JSP_VBL_OPEN)) |
| { |
| // we appear to be inside "#{", so next should be a VBL_CONTENT if there's anything |
| // here to validate |
| final ITextRegion content = regionList.get(2); |
| if (content.getType() == DOMJSPRegionContexts.JSP_VBL_CONTENT) |
| { |
| final int contentStart = |
| parentRegion.getStartOffset(content); |
| final IStructuredDocumentContext elContext = |
| IStructuredDocumentContextFactory.INSTANCE. |
| getContext(context.getStructuredDocument(), |
| contentStart); |
| |
| final String elText = parentRegion.getText(content); |
| |
| if (DEBUG) |
| System.out.println(addDebugSpacer(3)+"EL attrVal= "+elText); |
| |
| // EL validation is user configurable because |
| // it can be computationally costly. |
| if (checkShouldValidateEL(isIncremental)) |
| { |
| List elVals = MetaDataEnabledProcessingFactory.getInstance().getAttributeValueRuntimeTypeFeatureProcessors(IValidELValues.class, elContext, uri, tagName, attrName); |
| validateELExpression(context, |
| elContext, |
| elVals, |
| attrValue, |
| elText, |
| reporter, |
| file); |
| } |
| } |
| else if (content.getType() == DOMJSPRegionContexts.JSP_VBL_CLOSE) |
| { |
| final int offset = parentRegion.getStartOffset(openVBLQuote)+1; |
| final int length = 2; |
| |
| // detected empty EL expression |
| reporter.addMessage(this, |
| ValidationMessageFactory.createFromDiagnostic( |
| DiagnosticFactory.create_EMPTY_EL_EXPRESSION(), |
| offset, length, file)); |
| } |
| |
| boolean foundClosingQuote = false; |
| for (int i = 2; !foundClosingQuote && i < regionList.size(); i++) |
| { |
| ITextRegion searchRegion = regionList.get(i); |
| if (searchRegion.getType() == DOMJSPRegionContexts.JSP_VBL_CLOSE) |
| { |
| foundClosingQuote = true; |
| } |
| } |
| |
| if (!foundClosingQuote) |
| { |
| int offset = context.getDocumentPosition()+1; |
| int length = parentRegion.getText().length(); |
| |
| reporter.addMessage(this, |
| ValidationMessageFactory. |
| createFromDiagnostic( |
| DiagnosticFactory.create_MISSING_CLOSING_EXPR_BRACKET(), |
| offset, length, file)); |
| } |
| |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| private void validateELExpression(IStructuredDocumentContext context, |
| IStructuredDocumentContext elContext, |
| List elVals, |
| String attributeVal, |
| String elText, |
| IReporter reporter, |
| IFile file) |
| { |
| //Call EL validator which will perform at least the syntactical validation |
| final ELExpressionValidator elValidator = |
| new ELExpressionValidator(elContext, elText,file); |
| elValidator.validateXMLNode(); |
| elValidator.reportFindings(this, reporter); |
| |
| CompositeType exprType = elValidator.getExpressionType(); |
| if (exprType != null) |
| { |
| for (Iterator it=elVals.iterator();it.hasNext();){ |
| IValidELValues elval = (IValidELValues)it.next(); |
| CompositeType expectedType; |
| IMessage message = null; |
| try { |
| expectedType = elval.getExpectedRuntimeType(); |
| |
| if (expectedType != null) |
| { |
| Diagnostic status = TypeComparator.calculateTypeCompatibility |
| (expectedType, exprType); |
| if (status.getSeverity() != Diagnostic.OK){ |
| message = createValidationMessage(context, attributeVal, getSeverity(status.getSeverity()), status.getMessage(), file); |
| } |
| } |
| } catch (ELIsNotValidException e) { |
| message = createValidationMessage(context, attributeVal, IMessage.NORMAL_SEVERITY, e.getMessage(), file); |
| } |
| if (message != null) { |
| reporter.addMessage(this, message); |
| } |
| } |
| } |
| } |
| |
| // private void validateAttribute(IStructuredDocumentContext context, ITextRegion region, String uri, Node attr, IFile file) { |
| //Not doing anything until the resolver can help me |
| |
| //validate that attribute can be part of the tag |
| // ITaglibContextResolver tagLibResolver = IStructuredDocumentContextResolverFactory.INSTANCE.getTaglibContextResolver(context); |
| // if (tagLibResolver.getTagURIForNodeName(attr) == null){ |
| // System.out.println("not ok: "+attr.getNodeName()); |
| // } |
| // else |
| // System.out.println("ok"); |
| |
| // } |
| |
| /** |
| * Validates an attribute value in context using the JSF metadata processing framework |
| * |
| * @param context |
| * @param region |
| * @param uri |
| * @param tagName |
| * @param attrName |
| * @param attributeVal |
| * @param reporter |
| * @param file |
| */ |
| private void validateAttributeValue(IStructuredDocumentContext context, String uri, String tagName, String attrName, String attributeVal, IReporter reporter, IFile file) { |
| List vv = MetaDataEnabledProcessingFactory.getInstance().getAttributeValueRuntimeTypeFeatureProcessors(IValidValues.class, context, uri, tagName, attrName); |
| if (!vv.isEmpty()){ |
| for (Iterator it = vv.iterator();it.hasNext();){ |
| IValidValues v = (IValidValues)it.next(); |
| if (attributeVal == null) attributeVal = "";//ensure to be non-null |
| if (!v.isValidValue(attributeVal.trim())){ |
| if (DEBUG) |
| System.out.println(addDebugSpacer(4)+"NOT VALID "); |
| |
| for (Iterator msgs = v.getValidationMessages().iterator();msgs.hasNext();){ |
| IValidationMessage msg = (IValidationMessage)msgs.next(); |
| IMessage message = createValidationMessage(context, attributeVal, getSeverity(msg.getSeverity()), msg.getMessage(), file); |
| if (message != null) { |
| reporter.addMessage(this, message); |
| } |
| } |
| } |
| else |
| if (DEBUG) |
| System.out.println(addDebugSpacer(5) + "VALID "); |
| } |
| } |
| else if (DEBUG) |
| System.out.println(addDebugSpacer(4)+"NO META DATA "); |
| } |
| |
| private IMessage createValidationMessage(IStructuredDocumentContext context, String attributeValue, int severity, String msg, IFile file){ |
| IMessage message = new LocalizedMessage(severity, msg, file); |
| if (message != null) { |
| final int start = context.getDocumentPosition() + 1; |
| final int length = attributeValue.length(); |
| |
| int lineNo = 0; |
| try { |
| lineNo = context.getStructuredDocument().getLineOfOffset(start); |
| } catch (BadLocationException e) { |
| // TODO: C.B why need line number? Length and offset should be |
| // sufficient |
| } |
| |
| message.setLineNo(lineNo); |
| message.setOffset(start); |
| message.setLength(length); |
| } |
| return message; |
| } |
| /** |
| * Maps IStatus codes to IMessage severity |
| * @param IStatus codesseverity |
| * @return IMessage severity |
| */ |
| private int getSeverity(int severity) { |
| |
| switch (severity){ |
| case IStatus.ERROR: |
| return IMessage.HIGH_SEVERITY; |
| case IStatus.WARNING: |
| return IMessage.NORMAL_SEVERITY; |
| case IStatus.INFO: |
| return IMessage.LOW_SEVERITY; |
| } |
| return IMessage.NORMAL_SEVERITY; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.wst.sse.ui.internal.reconcile.validator.ISourceValidator#connect(org.eclipse.jface.text.IDocument) |
| */ |
| public void connect(IDocument document) { |
| fDocument = document; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.wst.sse.ui.internal.reconcile.validator.ISourceValidator#disconnect(org.eclipse.jface.text.IDocument) |
| */ |
| public void disconnect(IDocument document) { |
| // do nothing; no disconnect logic |
| } |
| |
| private String addDebugSpacer(int count){ |
| String TAB = "\t"; |
| StringBuffer ret = new StringBuffer(""); |
| for(int i=0;i<=count;i++){ |
| ret.append(TAB); |
| } |
| return ret.toString(); |
| } |
| |
| /** |
| * @param isIncremental -- true if this is "as-you-type" validation, false |
| * if this is "Build" or "Run Validation" validation |
| * @return true if user preferences say we should do EL validation, |
| * false otherwise |
| */ |
| private boolean checkShouldValidateEL(boolean isIncremental) |
| { |
| final ValidationPreferences prefs = new ValidationPreferences(); |
| prefs.load(); |
| if (isIncremental) |
| { |
| return prefs.getElPrefs().isEnableIncrementalValidation(); |
| } |
| |
| return prefs.getElPrefs().isEnableBuildValidation(); |
| } |
| |
| private void validateContainment(Element node, String uri, String tagName, IReporter reporter, IFile file, IStructuredDocumentContext context) |
| { |
| final IMetaDataModelContext modelContext = |
| new MetaDataModelContextImpl(file.getProject() |
| , MetaDataQueryHelper.TAGLIB_DOMAIN, uri); |
| final Entity entity = |
| MetaDataQueryHelper.getEntity(modelContext, tagName); |
| if (entity != null) |
| { |
| final Trait trait = |
| MetaDataQueryHelper.getTrait |
| (entity, "containment-constraint"); |
| |
| if (trait != null) |
| { |
| final ContainsTagConstraint tagConstraint = |
| (ContainsTagConstraint) trait.getValue(); |
| |
| final String algorithm = tagConstraint.getSetGenerator().getAlgorithm(); |
| |
| // TODO: need generalized factory mechanism for registering and constructing |
| // algorithms. |
| if (!"xpath".equals(algorithm)) |
| { |
| return; |
| } |
| |
| final String expr = tagConstraint.getSetGenerator().getExpression(); |
| |
| // TODO: optimize on the expression and cache for reuse |
| final XPathMatchingAlgorithm xpathAlg = |
| new XPathMatchingAlgorithm(expr); |
| |
| AxiomaticSet set = null; |
| |
| try |
| { |
| set = xpathAlg.evaluate(node); |
| // map dom nodes to tag identifiers |
| set = elem2TagIdMapper.map(set); |
| } |
| catch(InvalidExpressionException e) |
| { |
| JSFCorePlugin.log(e, "Problem with expression: "+expr+" on node "+node); |
| return; |
| } |
| catch (EvaluationException e) |
| { |
| JSFCorePlugin.log(e, "Problem evaluating expression: "+expr+" on node "+node); |
| return; |
| } |
| |
| final TagSet constraintData = tagConstraint.getSatisfiesSet(); |
| final AxiomaticSet constraintSet = new ConcreteAxiomaticSet(); |
| for (final Iterator it = constraintData.getTags().iterator(); it.hasNext();) |
| { |
| final TagId tagId = (TagId) it.next(); |
| constraintSet.add(TagIdentifierFactory.createJSPTagWrapper(tagId.getUri(), tagId.getName())); |
| } |
| final MemberConstraint memberConstraint = new MemberConstraint(constraintSet); |
| final Diagnostic diag = memberConstraint.isSatisfied(set); |
| |
| if (diag.getSeverity() != Diagnostic.OK) |
| { |
| final String messagePattern = "Tag {0} is missing required parent tag \"{1}\" ({2})"; |
| |
| List data = diag.getData(); |
| |
| for (Iterator it = data.iterator(); it.hasNext();) |
| { |
| TagIdentifier missingParent = (TagIdentifier) it.next(); |
| |
| IMessage message = |
| createTagValidationMessage(context, node.getNodeName(), |
| IMessage.NORMAL_SEVERITY, |
| MessageFormat.format(messagePattern, new Object[]{node.getNodeName(), missingParent.getTagName(), missingParent.getUri()}) |
| , file); |
| reporter.addMessage(this, message); |
| } |
| } |
| } |
| } |
| } |
| |
| // TODO: need a diagnostic factory |
| private IMessage createTagValidationMessage(IStructuredDocumentContext context, String attributeValue, int severity, String msg, IFile file) |
| { |
| IMessage message = new LocalizedMessage(severity, msg, file); |
| final int start = context.getDocumentPosition(); |
| final int length = attributeValue.length(); |
| |
| message.setOffset(start); |
| message.setLength(length); |
| return message; |
| } |
| } |