/*******************************************************************************
* Copyright (c) 2007, 2014 Spring IDE Developers
* 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:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.core.model.validation;
import java.util.LinkedHashSet;
import java.util.Set;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.springframework.ide.eclipse.core.MarkerUtils;
import org.springframework.ide.eclipse.core.internal.model.validation.ValidationRuleDefinition;
import org.springframework.ide.eclipse.core.internal.model.validation.ValidationRuleDefinitionFactory;
import org.springframework.ide.eclipse.core.model.IModelElement;
import org.springframework.ide.eclipse.core.model.IModelElementVisitor;
import org.springframework.ide.eclipse.core.model.IResourceModelElement;
import org.springframework.ide.eclipse.core.project.DefaultProjectContributorState;
import org.springframework.ide.eclipse.core.project.IProjectContributorState;
import org.springframework.ide.eclipse.core.project.IProjectContributorStateAware;
/**
* Base {@link IValidator} implementation that abstracts model visiting and provides implementation hooks for sub
* classes.
* @author Torsten Juergeleit
* @author Christian Dupuis
* @author Martin Lippert
* @since 2.0
*/
public abstract class AbstractValidator implements IValidator, IProjectContributorStateAware {
/** Internal state object */
private IProjectContributorState contributorState;
/**
* unique id that should be used to identify the markers created by this validator
*/
private String markerId;
/** unique id for this validator */
private String validatorId;
/**
* {@inheritDoc}
*/
public void cleanup(IResource resource, IProgressMonitor monitor) throws CoreException {
MarkerUtils.deleteMarkers(resource, getMarkerId());
}
public void setMarkerId(String markerId) {
this.markerId = markerId;
}
/**
* {@inheritDoc}
*/
public void setProjectContributorState(IProjectContributorState contributorState) {
this.contributorState = contributorState;
this.contributorState.hold(new ValidationProgressState());
}
public void setValidatorId(String validatorId) {
this.validatorId = validatorId;
}
/**
* {@inheritDoc}
*/
public final void validate(Set<IResource> affectedResources, int kind, IProgressMonitor monitor)
throws CoreException {
SubProgressMonitor subMonitor = new SubProgressMonitor(monitor, affectedResources.size());
try {
for (IResource resource : affectedResources) {
String progressMessage = "Validating '" + resource.getFullPath().toString().substring(1) + "'";
reportProgress(progressMessage, subMonitor);
cleanup(resource, subMonitor);
if (subMonitor.isCanceled()) {
throw new OperationCanceledException();
}
IValidationElementLifecycleManager callback = initValidationElementCallback(resource, kind);
IResourceModelElement rootElement = callback.getRootElement();
// Check if resource model element is external to the workspace -> if so, do not validate the resource
if (rootElement != null && rootElement.isExternal()) {
monitor.worked(1);
break;
}
Set<ValidationRuleDefinition> ruleDefinitions = getRuleDefinitions(resource);
if (rootElement != null && ruleDefinitions != null && ruleDefinitions.size() > 0) {
Set<ValidationProblem> problems = validate(callback, ruleDefinitions, subMonitor);
ValidationUtils.createProblemMarkers(resource, problems, getMarkerId());
}
// call close on callback to execute any required resource cleanup in template
callback.destroy();
subMonitor.worked(1);
if (subMonitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}
finally {
subMonitor.done();
}
}
private IValidationElementLifecycleManager initValidationElementCallback(IResource resource, int kind) {
IValidationElementLifecycleManager callback = createValidationElementLifecycleManager();
if (callback instanceof IValidationElementLifecycleManagerExtension) {
((IValidationElementLifecycleManagerExtension) callback).setKind(kind);
}
callback.init(resource);
return callback;
}
private Set<ValidationProblem> validate(IValidationElementLifecycleManager callback,
Set<ValidationRuleDefinition> ruleDefinitions, SubProgressMonitor subMonitor) {
Set<ValidationProblem> problems = new LinkedHashSet<ValidationProblem>();
for (IResourceModelElement contextElement : callback.getContextElements()) {
IValidationContext context = createContext(callback.getRootElement(), contextElement);
if (context instanceof IProjectContributorStateAware) {
((IProjectContributorStateAware) context).setProjectContributorState(contributorState);
}
if (context != null) {
IModelElementVisitor visitor = new ValidationVisitor(context, ruleDefinitions);
callback.getRootElement().accept(visitor, subMonitor);
problems.addAll(context.getProblems());
}
if (subMonitor.isCanceled()) {
throw new OperationCanceledException();
}
}
return problems;
}
/**
* Returns a newly created {@link IValidationContext} for the given {@link IResourceModelElement root element} and
* it's {@link IResourceModelElement context element}.
*/
protected abstract IValidationContext createContext(IResourceModelElement rootElement,
IResourceModelElement contextElement);
/**
* Returns {@link IValidationElementLifecycleManager}.
*/
protected abstract IValidationElementLifecycleManager createValidationElementLifecycleManager();
/**
* Returns the ID of this validator's {@link IMarker validation problem marker} ID.
*/
protected String getMarkerId() {
return markerId;
}
/**
* Returns the {@link IProjectContributorState}.
*/
protected synchronized IProjectContributorState getProjectContributorState() {
if (contributorState == null) {
contributorState = new DefaultProjectContributorState();
}
return contributorState;
}
/**
* Returns the list of enabled {@link ValidationRuleDefinition}s for this validator.
*/
protected Set<ValidationRuleDefinition> getRuleDefinitions(IResource resource) {
return ValidationRuleDefinitionFactory.getEnabledRuleDefinitions(getValidatorId(), resource.getProject());
}
/**
* Returns the validator id
*/
protected String getValidatorId() {
return validatorId;
}
/**
* Report the progress against the given <code>monitor</code>.
*/
protected void reportProgress(String message, IProgressMonitor monitor, Object... args) {
reportProgress(String.format(message, args), monitor);
}
/**
* Report the progress against the given <code>monitor</code>.
*/
protected void reportProgress(String message, IProgressMonitor monitor) {
ValidationProgressState progress = getProjectContributorState().get(ValidationProgressState.class);
if (progress != null) {
int errorCount = progress.getErrorCount();
int warningCount = progress.getWarningCount();
if (errorCount > 0 || warningCount > 0) {
StringBuilder builder = new StringBuilder("(Found ");
if (errorCount > 0) {
builder.append(errorCount).append((errorCount > 1 ? " errors" : " error"));
}
if (errorCount > 0 && warningCount > 0) {
builder.append(" + ");
}
if (warningCount > 0) {
builder.append(warningCount).append((warningCount > 1 ? " warnings" : " warning"));
}
builder.append(") ").append(message);
monitor.subTask(builder.toString());
}
else {
monitor.subTask(message);
}
}
else {
monitor.subTask(message);
}
}
/**
* Returns <code>true</code> if this validator is able to validate the given element.
*/
protected abstract boolean supports(IModelElement element);
/**
* provide a hook method to allow validators to skip certain elements during validation
*
* @param element
* @param validationContext
* @return
*/
protected boolean shouldValidate(IModelElement element, IValidationContext validationContext) {
return true;
}
/**
* {@link IModelElementVisitor} implementation that validates a specified model tree.
*/
protected final class ValidationVisitor implements IModelElementVisitor {
private IValidationContext context;
private Set<ValidationRuleDefinition> ruleDefinitions;
public ValidationVisitor(IValidationContext context, Set<ValidationRuleDefinition> ruleDefinitions) {
this.ruleDefinitions = ruleDefinitions;
this.context = context;
}
@SuppressWarnings("unchecked")
public boolean visit(IModelElement element, IProgressMonitor monitor) {
if (supports(element) && shouldValidate(element, context)) {
SubProgressMonitor subMonitor = new SubProgressMonitor(monitor, ruleDefinitions.size());
try {
for (ValidationRuleDefinition ruleDefinition : ruleDefinitions) {
if (subMonitor.isCanceled()) {
throw new OperationCanceledException();
}
String progressMessage = "Validating element '" + element.getElementName() + "' with rule '" + ruleDefinition.getName() + "'";
reportProgress(progressMessage, subMonitor);
IValidationRule rule = ruleDefinition.getRule();
if (rule.supports(element, context)) {
context.setCurrentRuleDefinition(ruleDefinition);
rule.validate(element, context, monitor);
}
subMonitor.worked(1);
}
}
finally {
subMonitor.done();
}
return true;
}
return false;
}
}
}