/*******************************************************************************
* Copyright (c) 2013 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.jsf.web.validation;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.text.IDocument;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.validation.internal.core.ValidationException;
import org.eclipse.wst.validation.internal.provisional.core.IReporter;
import org.eclipse.wst.validation.internal.provisional.core.IValidator;
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.IDOMModel;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.jboss.tools.common.el.core.resolver.ELContext;
import org.jboss.tools.common.validation.ContextValidationHelper;
import org.jboss.tools.common.validation.IProjectValidationContext;
import org.jboss.tools.common.validation.IValidatingProjectTree;
import org.jboss.tools.common.validation.PreferenceInfoManager;
import org.jboss.tools.common.validation.ValidatorManager;
import org.jboss.tools.jsf.JSFModelPlugin;
import org.jboss.tools.jsf.project.JSFNature;
import org.jboss.tools.jsf.web.validation.composite.CompositeComponentValidator;
import org.jboss.tools.jst.web.kb.IPageContext;
import org.jboss.tools.jst.web.kb.KbQuery;
import org.jboss.tools.jst.web.kb.KbQuery.Type;
import org.jboss.tools.jst.web.kb.PageContextFactory;
import org.jboss.tools.jst.web.kb.PageProcessor;
import org.jboss.tools.jst.web.kb.internal.KbBuilder;
import org.jboss.tools.jst.web.kb.internal.validation.KBValidator;
import org.jboss.tools.jst.web.kb.internal.validation.WebValidator;
import org.jboss.tools.jst.web.kb.taglib.IAttribute;
import org.jboss.tools.jst.web.kb.taglib.IComponent;
import org.jboss.tools.jst.web.kb.taglib.INameSpace;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* @author Alexey Kazakov
*/
public class StrictTaglibValidator extends WebValidator {
public static final String ID = "org.jboss.tools.jsf.StrictTagLibValidator"; //$NON-NLS-1$
// Project libraries cache: Map<String url, Map<String tagName, Set<String> attributes>>>
private Map<String, Map<String, Set<String>>> cache;
private boolean shouldValidateTagLibTags;
private boolean shouldValidateTagLibTagAttributes;
@Override
public void init(IProject project,
ContextValidationHelper validationHelper,
IProjectValidationContext context, IValidator manager,
IReporter reporter) {
super.init(project, validationHelper, context, manager, reporter);
cache = new HashMap<String, Map<String, Set<String>>>();
shouldValidateTagLibTags = shouldValidateTagLibTags(project);
shouldValidateTagLibTagAttributes = shouldValidateTagLibTagAttributes(project);
}
/* (non-Javadoc)
* @see org.jboss.tools.common.validation.IValidator#validate(java.util.Set, org.eclipse.core.resources.IProject, org.jboss.tools.common.validation.ContextValidationHelper, org.jboss.tools.common.validation.IProjectValidationContext, org.jboss.tools.common.validation.ValidatorManager, org.eclipse.wst.validation.internal.provisional.core.IReporter)
*/
@Override
public IStatus validate(Set<IFile> changedFiles, IProject project,
ContextValidationHelper validationHelper,
IProjectValidationContext validationContext,
ValidatorManager manager, IReporter reporter)
throws ValidationException {
init(project, validationHelper, validationContext, manager, reporter);
Set<IFile> filesToValidate = new HashSet<IFile>();
for (IFile file : changedFiles) {
if(notValidatedYet(file) && shouldBeValidated(file)) {
filesToValidate.add(file);
}
}
for (IFile file : filesToValidate) {
validateFile(file);
}
cache = null;
return OK_STATUS;
}
/* (non-Javadoc)
* @see org.jboss.tools.common.validation.IValidator#validateAll(org.eclipse.core.resources.IProject, org.jboss.tools.common.validation.ContextValidationHelper, org.jboss.tools.common.validation.IProjectValidationContext, org.jboss.tools.common.validation.ValidatorManager, org.eclipse.wst.validation.internal.provisional.core.IReporter)
*/
@Override
public IStatus validateAll(IProject project,
ContextValidationHelper validationHelper,
IProjectValidationContext validationContext,
ValidatorManager manager, IReporter reporter)
throws ValidationException {
init(project, validationHelper, validationContext, manager, reporter);
Set<IFile> files = validationHelper.getProjectSetRegisteredFiles();
Set<IFile> filesToValidate = new HashSet<IFile>();
for (IFile file : files) {
if(notValidatedYet(file) && shouldBeValidated(file)) {
filesToValidate.add(file);
}
}
for (IFile file : filesToValidate) {
validateFile(file);
}
cache = null;
return OK_STATUS;
}
private void validateFile(IFile file) {
if(reporter.isCancelled()) {
return;
}
ELContext context = PageContextFactory.createPageContext(file);
if (context instanceof IPageContext) {
IModelManager manager = StructuredModelManager.getModelManager();
if(manager != null) {
IStructuredModel model = null;
try {
model = manager.getModelForRead(file);
if (model instanceof IDOMModel) {
displaySubtask(JSFValidationMessage.VALIDATING_RESOURCE, new String[]{file.getProject().getName(), file.getName()});
coreHelper.getValidationContextManager().addValidatedProject(this, file.getProject());
removeAllMessagesFromResource(file);
IDOMDocument domDocument = ((IDOMModel) model).getDocument();
validateChildNodes(file, domDocument.getStructuredDocument(),
domDocument, (IPageContext)context);
}
} catch (CoreException e) {
JSFModelPlugin.getDefault().logError(e);
} catch (IOException e) {
JSFModelPlugin.getDefault().logError(e);
} finally {
if (model != null) {
model.releaseFromRead();
}
}
}
}
}
private void validateChildNodes(IFile file, IDocument document, IDOMNode parent, IPageContext context) {
NodeList children = parent.getChildNodes();
for(int i = 0; children != null && i < children.getLength(); i++) {
Node child = children.item(i);
if (child instanceof IDOMNode) {
validateNode(file, document, (IDOMNode)child, context);
validateChildNodes(file, document, (IDOMNode)child, context);
}
}
}
private void validateNode(IFile file, IDocument document, IDOMNode node, IPageContext context) {
if (!(node instanceof IDOMElement))
return;
String nodeName = node.getNodeName();
int offset = node.getStartOffset();
int prefixDivider = nodeName.indexOf(':');
if (prefixDivider == -1)
return;
String prefix = nodeName.substring(0, prefixDivider);
String name = nodeName.substring(prefixDivider + 1);
String uri = getUri(context, prefix, offset);
// Check that tag exists
// If the tag isn't cached - get its attributes and cache them
Map<String, Set<String>> tagCache = cache.get(uri);
if (tagCache == null) {
tagCache = new HashMap<String, Set<String>>();
cache.put(uri, tagCache);
}
if (!tagCache.containsKey(nodeName)) {
KbQuery kbQuery = new KbQuery();
kbQuery.setPrefix(prefix);
kbQuery.setUri(uri);
kbQuery.setMask(false);
kbQuery.setType(Type.TAG_NAME);
kbQuery.setOffset(offset);
kbQuery.setValue(nodeName);
IComponent[] components = PageProcessor.getInstance().getComponents(kbQuery, context, false);
if (components.length > 0) {
Set<String> tagAttributes = new HashSet<String>();
tagCache.put(name, tagAttributes);
for (IComponent comp : components) {
IAttribute[] attributes = comp.getAttributes();
for (IAttribute attribute : attributes) {
tagAttributes.add(attribute.getName());
}
}
}
}
if (!tagCache.containsKey(name)) {
if(shouldValidateTagLibTags) {
if (null == unknownTag(file, offset + 1, nodeName.length(), nodeName)) {
return; // if unknownTag() returns null then no further validation is needed
}
}
} else if (shouldValidateTagLibTagAttributes) {
NamedNodeMap nodeAttributes = node.getAttributes();
if (nodeAttributes != null && nodeAttributes.getLength() > 0) {
Set<String> tagAttributes = tagCache.get(name);
for (int i = 0; i < nodeAttributes.getLength(); i++) {
Node attribute = nodeAttributes.item(i);
String attributeName = attribute.getNodeName();
int attributeOffset = (attribute instanceof IDOMNode ? ((IDOMNode)attribute).getStartOffset() : offset);
if (tagAttributes == null || !tagAttributes.contains(attributeName)) {
if (null == unknownAttribute(file, attributeOffset, attributeName.length(), nodeName, attributeName))
return; // if unknownAttribute() returns null then no further validation is needed
}
}
}
}
}
private String getUri(IPageContext context, String prefix, int offset) {
if (prefix == null)
return null;
Map<String, List<INameSpace>> nameSpaces = context.getNameSpaces(offset);
if (nameSpaces == null || nameSpaces.isEmpty())
return null;
for (List<INameSpace> nameSpace : nameSpaces.values()) {
for (INameSpace n : nameSpace) {
if (prefix.equals(n.getPrefix())) {
return n.getURI();
}
}
}
return null;
}
private IMarker unknownAttribute(IFile target, int offset, int length, String tagName, String attributeName) {
return addProblem(JSFValidationMessage.UNKNOWN_TAGLIB_COMPONENT_ATTRIBUTE, JSFSeverityPreferences.UNKNOWN_TAGLIB_ATTRIBUTE, new String[]{attributeName, tagName}, length, offset, target);
}
private IMarker unknownTag(IFile target, int offset, int length, String tagName) {
return addProblem(JSFValidationMessage.UNKNOWN_TAGLIB_COMPONENT_NAME, JSFSeverityPreferences.UNKNOWN_TAGLIB_COMPONENT, new String[]{tagName}, length, offset, target);
}
/* (non-Javadoc)
* @see org.jboss.tools.common.validation.IValidator#getId()
*/
@Override
public String getId() {
return ID;
}
/* (non-Javadoc)
* @see org.jboss.tools.common.validation.IValidator#getBuilderId()
*/
@Override
public String getBuilderId() {
return KbBuilder.BUILDER_ID;
}
/* (non-Javadoc)
* @see org.jboss.tools.jst.web.kb.internal.validation.WebValidator#shouldValidateJavaSources()
*/
@Override
protected boolean shouldValidateJavaSources() {
return false;
}
private static final String BUNDLE_NAME = "org.jboss.tools.jsf.web.validation.messages";
/* (non-Javadoc)
* @see org.jboss.tools.common.validation.TempMarkerManager#getMessageBundleName()
*/
@Override
protected String getMessageBundleName() {
return BUNDLE_NAME;
}
/*
* (non-Javadoc)
* @see org.jboss.tools.jst.web.kb.validation.IValidator#getValidatingProjects(org.eclipse.core.resources.IProject)
*/
public IValidatingProjectTree getValidatingProjects(IProject project) {
return createSimpleValidatingProjectTree(project);
}
/*
* (non-Javadoc)
* @see org.jboss.tools.jst.web.kb.validation.IValidator#shouldValidate(org.eclipse.core.resources.IProject)
*/
public boolean shouldValidate(IProject project) {
try {
return project != null
&& project.isAccessible()
&& project.hasNature(JSFNature.NATURE_ID)
&& validateBuilderOrder(project)
&& isEnabled(project);
} catch (CoreException e) {
JSFModelPlugin.getDefault().logError(e);
}
return false;
}
private boolean validateBuilderOrder(IProject project) throws CoreException {
return KBValidator.validateBuilderOrder(project, getBuilderId(), getId(), JSFSeverityPreferences.getInstance());
}
/*
* (non-Javadoc)
* @see org.jboss.tools.jst.web.kb.validation.IValidator#isEnabled(org.eclipse.core.resources.IProject)
*/
public boolean isEnabled(IProject project) {
return JSFSeverityPreferences.isValidationEnabled(project) && JSFSeverityPreferences.shouldValidateTagLibs(project);
}
/*
* (non-Javadoc)
* @see org.jboss.tools.jst.web.kb.internal.validation.ValidationErrorManager#getPreference(org.eclipse.core.resources.IProject, java.lang.String)
*/
protected String getPreference(IProject project, String preferenceKey) {
return JSFSeverityPreferences.getInstance().getProjectPreference(project, preferenceKey);
}
protected boolean shouldValidateTagLibTags(IProject project) {
return JSFSeverityPreferences.shouldValidateTagLibTags(project);
}
protected boolean shouldValidateTagLibTagAttributes(IProject project) {
return JSFSeverityPreferences.shouldValidateTagLibTagAttributes(project);
}
/*
* (non-Javadoc)
* @see org.jboss.tools.jst.web.kb.internal.validation.ValidationErrorManager#getMaxNumberOfMarkersPerFile(org.eclipse.core.resources.IProject)
*/
public int getMaxNumberOfMarkersPerFile(IProject project) {
return JSFSeverityPreferences.getMaxNumberOfProblemMarkersPerFile(project);
}
@Override
public void registerPreferenceInfo() {
PreferenceInfoManager.register(getProblemType(), new CompositeComponentValidator.CompositeComponentPreferenceInfo());
}
}