blob: 9449b90d4075fdf747ccd961a109b73bd0dbd6df [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2010 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
*******************************************************************************/
package org.eclipse.wst.validation;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IContributor;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.osgi.util.NLS;
import org.eclipse.wst.validation.internal.ConfigurationConstants;
import org.eclipse.wst.validation.internal.ConfigurationManager;
import org.eclipse.wst.validation.internal.ContentTypeWrapper;
import org.eclipse.wst.validation.internal.ExtensionConstants;
import org.eclipse.wst.validation.internal.MarkerManager;
import org.eclipse.wst.validation.internal.Misc;
import org.eclipse.wst.validation.internal.NullValidator;
import org.eclipse.wst.validation.internal.SummaryReporter;
import org.eclipse.wst.validation.internal.Tracing;
import org.eclipse.wst.validation.internal.ValManager;
import org.eclipse.wst.validation.internal.ValMessages;
import org.eclipse.wst.validation.internal.ValOperation;
import org.eclipse.wst.validation.internal.ValPrefManagerGlobal;
import org.eclipse.wst.validation.internal.ValPrefManagerProject;
import org.eclipse.wst.validation.internal.ValType;
import org.eclipse.wst.validation.internal.ValidationConfiguration;
import org.eclipse.wst.validation.internal.ValidatorExtensionReader;
import org.eclipse.wst.validation.internal.ValidatorMetaData;
import org.eclipse.wst.validation.internal.core.ValidatorLauncher;
import org.eclipse.wst.validation.internal.delegates.ValidatorDelegateDescriptor;
import org.eclipse.wst.validation.internal.delegates.ValidatorDelegatesRegistry;
import org.eclipse.wst.validation.internal.model.FilterGroup;
import org.eclipse.wst.validation.internal.operations.IWorkbenchContext;
import org.eclipse.wst.validation.internal.operations.WorkbenchContext;
import org.eclipse.wst.validation.internal.plugin.ValidationPlugin;
import org.eclipse.wst.validation.internal.provisional.core.IMessage;
import org.eclipse.wst.validation.internal.provisional.core.IValidator;
import org.eclipse.wst.validation.internal.provisional.core.IValidatorExtender;
/**
* Represents a validator. This gets instantiated through one of the validator extension points.
* <p>
* <b>This class is not API</b>.
* </p>
*
* @author karasiuk
*
*/
public abstract class Validator implements Comparable<Validator> {
// Remember if you add a new instance variable, make sure that you update the copy and become methods
/**
* The level of configuration for the validator.
* <ul>
* <li>Extension - Defined by an extension point.</li>
* <li>Global - Defined by a global preference.</li>
* <li>Project - Defined by a project property.</li>
* </ul>
*/
public enum Level {Extension, Global, Project};
protected boolean _buildValidation = true;
/** If this is a delegating validator, then this field holds the validator that will be delegated to. */
private String _delegatingId;
/**
* If this validator is also used to control an ISource validator, the id of the ISource validator is
* registered here.
*/
private String _sourceId;
protected boolean _manualValidation = true;
/** An optional customized marker id for this validator. */
private String _markerId;
/**
* Version of the filter definition. By increasing this number the framework can know that a plug-in has
* changed it's filters.
*/
private int _version = 1;
/** Map simple message id's to message settings. */
private Map<String, MessageSeveritySetting> _messageSettings;
/** The project that you are defined in. This can be null which means that you are a global validator. */
protected IProject _project;
/** How many times has a global field in this validator been changed since it was created (or copied)? */
protected transient int _changeCountGlobal;
/** How many times has a message field in this validator been changed since it was created (or copied)? */
protected transient int _changeCountMessages;
/** Has the validator been migrated from an earlier version in this session, but not yet saved? */
private boolean _migrated;
public Validator(IProject project){
_project = project;
}
void setMigrated(boolean migrated){
_migrated = migrated;
}
/**
* Create a new validator based on a abstract validator.
*
* @param validator
* The validator that is being wrapped.
*
* @param project
* The project that you are defined in. This can be null which
* means that you are a global validator.
*/
public static Validator create(IConfigurationElement validator, IProject project) {
V2 v2 = new V2(validator, project);
return v2;
}
/**
* Create a new validator based on validator meta data.
*
* @param project
* The project that you are defined in. This can be null which
* means that you are a global validator.
*/
public static Validator create(ValidatorMetaData vmd, ValidationConfiguration config, IProject project){
V1 v1 = new V1(vmd, config, project);
return v1;
}
/**
* If you are a version 1 validator, answer yourself as one, otherwise answer null.
*/
public V1 asV1Validator(){
return null;
}
/**
* If you are a version 2 validator, answer yourself as one, otherwise answer null.
*/
public V2 asV2Validator() {
return null;
}
/**
* The project is being cleaned, this method gives the validator a chance to do any special cleanup.
* The default is to do nothing.
*
* @param project the project being built.
* @param monitor the monitor that should be used for reporting progress if the clean takes a long time.
*/
public void clean(IProject project, ValOperation operation, IProgressMonitor monitor){
}
/**
* Compare yourself based on Validator name.
*/
public int compareTo(Validator validator) {
return getName().compareTo(validator.getName());
}
/** Answer a deep copy of yourself. */
public Validator copy(){
return copy(false);
}
public abstract Validator copy(boolean includeChangeCounts);
/**
* Update your direct, non transient fields from the fields in v.
*/
protected void copyLocal(Validator v, boolean includeChangeCounts){
_buildValidation = v._buildValidation;
_delegatingId = v._delegatingId;
_manualValidation = v._manualValidation;
_markerId = v._markerId;
_messageSettings = v._messageSettings;
_sourceId = v._sourceId;
_version = v._version;
_migrated = v._migrated;
if (includeChangeCounts){
_changeCountGlobal = v._changeCountGlobal;
_changeCountMessages = v._changeCountMessages;
}
}
/**
* Should the validation framework first clear the markers that this
* validator has placed on this resource? This method can be overridden by
* validator implementors to provide a validator specific behavior.
*
* @param event
* The validation event that triggered the validation.
* @return true if the validation framework should first clear all the
* markers that this validator produced. This is the default
* behavior. Return false to leave the markers unchanged. It then
* becomes the responsibility of the validator to manage it's own
* markers for this resource, for this validation event.
*/
public boolean shouldClearMarkers(ValidationEvent event){
return true;
}
/**
* Answer true if this validator, based on it's filters, should validate
* this resource. This method does not check to see if global validation or
* project validation has been suspended or not.
*
* @param resource
* The resource to be checked.
* @param isManual
* If true then this validator must also be enabled for manual
* validation.
* @param isBuild
* If true then this validator must also be enabled for builder
* based validation.
*
* @return true if the resource should be validated.
*/
public boolean shouldValidate(IResource resource, boolean isManual, boolean isBuild){
return shouldValidate(resource, isManual, isBuild, new ContentTypeWrapper());
}
/**
* Answer true if this validator, based on it's filters, should validate
* this resource. This method does not check to see if global validation or
* project validation has been suspended or not.
*
* @param resource
* The resource to be checked.
* @param isManual
* If true then this validator must also be enabled for manual
* validation.
* @param isBuild
* If true then this validator must also be enabled for builder
* based validation.
* @param contentTypeWrapper
* For repeated calls on the same resource, it is more efficient
* to remember the content type.
* @return true if the resource should be validated.
* @see Friend#shouldValidate(Validator, IResource, boolean, boolean, ContentTypeWrapper)
*/
boolean shouldValidate(IResource resource, boolean isManual, boolean isBuild,
ContentTypeWrapper contentTypeWrapper){
if (isManual && !_manualValidation)return false;
if (isBuild && !_buildValidation)return false;
return shouldValidate(resource, contentTypeWrapper);
}
/**
* Answer true if this validator, based on it's filters, should validate
* this resource. This method does not check to see if global validation or
* project validation has been suspended or not.
*
* @param resource
* The resource to be checked.
* @param valType
* The context to use when performing the check.
*
* @return true if the resource should be validated.
*/
public boolean shouldValidate(IResource resource, ValType valType){
return shouldValidate(resource, valType, new ContentTypeWrapper());
}
/**
* Answer true if this validator, based on it's filters, should validate
* this resource. This method does not check to see if global validation or
* project validation has been suspended or not.
*
* @param resource
* The resource to be checked.
* @param valType
* The context to use when performing the check.
* @param contentTypeWrapper
* For repeated calls on the same resource, it is more efficient
* to remember the content type.
*
* @return true if the resource should be validated.
*
* @see Friend#shouldValidate(Validator, IResource, ValType, ContentTypeWrapper)
*/
boolean shouldValidate(IResource resource, ValType valType, ContentTypeWrapper contentTypeWrapper){
if (Tracing.matchesExtraDetail(getId())){
Tracing.log("Validator-01: checking if " + getId() + " should validate " + resource); //$NON-NLS-1$ //$NON-NLS-2$
}
if (valType == ValType.Manual && !_manualValidation)return false;
if (valType == ValType.Build && !_buildValidation)return false;
boolean result = shouldValidate(resource, contentTypeWrapper);
if (Tracing.matchesExtraDetail(getId())){
Tracing.log("Validator-02: result = " + result); //$NON-NLS-1$
}
return result;
}
/**
* Answer true if this validator, based on it's filters, should validate this project. This method
* does not check to see if global validation or project validation has been suspended or not.
*
* @param project the project to be checked
* @param type The type of validation request
*
* @return true if the project should be validated.
*/
public boolean shouldValidateProject(IProject project, ValType type){
return shouldValidateProject(project, type == ValType.Manual, type == ValType.Build);
}
/**
* Answer true if this validator, based on it's filters, should validate this project. This method
* does not check to see if global validation or project validation has been suspended or not.
*
* @param project the project to be checked
* @param isManual if true then this validator must also be enabled for manual validation.
* @param isBuild if true then this validator must also be enabled for builder based validation.
*
* @return true if the project should be validated.
*/
public boolean shouldValidateProject(IProject project, boolean isManual, boolean isBuild){
if (Tracing.matchesExtraDetail(getId())){
Tracing.log("Validator-03: checking if " + getId() + " should validate " + project); //$NON-NLS-1$ //$NON-NLS-2$
}
if (isManual && !_manualValidation)return false;
if (isBuild && !_buildValidation)return false;
if (project == null || !project.isOpen())return false;
boolean result = shouldValidateProject(project);
if (Tracing.matchesExtraDetail(getId())){
Tracing.log("Validator-04: result " + result); //$NON-NLS-1$
}
return result;
}
/**
* Validate the resource.
*
* @param resource
* The resource to be validated.
* @param kind
* The kind of resource change, see IResourceDelta for values.
* @param operation
* The operation that this validation is running under. This can
* be null.
* @param monitor
* A way to report progress. This can be null.
*
* @return the result of doing the validation, it can be, but usually isn't
* null.
*/
public abstract ValidationResult validate(IResource resource, int kind, ValOperation operation, IProgressMonitor monitor);
/**
* Validate the resource.
*
* @param resource
* The resource to be validated.
* @param kind
* The kind of resource change, see IResourceDelta for values.
* @param operation
* The operation that this validation is running under. This can
* be null.
* @param monitor
* A way to report progress. This can be null.
* @param event
* An event that describes in more detail what should be
* validated and why it should be validated. This can be null.
*
* @return the result of doing the validation, it can be, but usually isn't
* null.
*/
public ValidationResult validate(IResource resource, int kind, ValOperation operation, IProgressMonitor monitor, ValidationEvent event){
// The reason that the resource and kind are still specified, is that I didn't want to remove a public method in the service
// stream.
return validate(resource, kind, operation, monitor);
}
/**
* This method will be called before any validation takes place. It allows validators to perform any
* initialization that they might need.
*
* @param project the project that is being validated. For the very first call in the validation phase,
* this will be null. That is the signal to the validator that a top level validation is starting.
* Subsequently, the project will be set, as each of the individual projects are validated.
*
* @param state a way to pass arbitrary, validator specific, data from one invocation of a validator to
* the next, during the validation phase.
*
* @param monitor the monitor that should be used for reporting progress if the clean takes a long time.
*/
public void validationStarting(IProject project, ValidationState state, IProgressMonitor monitor){
// subclasses need to override this, if they wish to let their validators know about this event
}
/**
* This method will be called when validation is complete. It allows validators to perform any
* cleanup that they might need to do.
*
* @param project the project that was validated. The very last call in the validation will set this to
* null so that the validator knows that all the projects have now been validated.
*
* @param state a way to pass arbitrary, validator specific, data from one invocation of a validator to
* the next, during the validation phase.
*
* @param monitor the monitor that should be used for reporting progress if the clean takes a long time.
*/
public void validationFinishing(IProject project, ValidationState state, IProgressMonitor monitor){
// subclasses need to override this, if they wish to let their validators know about this event
}
void add(MessageSeveritySetting message){
// I can't use getMessageSettings() here, as that will put us into an infinite loop
if (_messageSettings == null){
_messageSettings = new HashMap<String, MessageSeveritySetting>(10);
}
_messageSettings.put(message.getId(), message);
bumpChangeCountMessages();
}
public IValidator asIValidator(){
return null;
}
protected abstract boolean shouldValidate(IResource resource, ContentTypeWrapper contentTypeWrapper);
protected abstract boolean shouldValidateProject(IProject project);
public abstract String getId();
public MessageSeveritySetting getMessage(String id){
return getMessageSettings().get(id);
}
/**
* Answer all the message settings that this validator has defined.
*
* @return an empty map if the validator did not define any message settings.
*/
public Map<String, MessageSeveritySetting> getMessageSettings(){
Map<String, MessageSeveritySetting> settings = _messageSettings;
if (settings == null){
settings = new HashMap<String, MessageSeveritySetting>(10);
init(settings);
if (ValManager.getDefault().mustUseGlobalValidators(getProject())){
ValPrefManagerGlobal gp = ValPrefManagerGlobal.getDefault();
gp.loadMessages(this, settings);
}
else {
ValPrefManagerProject vp = new ValPrefManagerProject(getProject());
vp.loadMessages(this, settings);
}
_messageSettings = settings;
}
return settings;
}
/**
* Answer a hash code for the configurable fields so that we can quickly determine if two
* validators are the same.
*/
public int hashCodeForConfig(){
int h = 0;
if (_buildValidation)h += 101;
if (_delegatingId != null)h += _delegatingId.hashCode();
if (_manualValidation)h += 201;
if (_messageSettings != null){
for (MessageSeveritySetting ms : _messageSettings.values())h += ms.hashCode();
}
if (_sourceId != null)h += _sourceId.hashCode();
h += _version;
return h;
}
private void init(Map<String, MessageSeveritySetting> settings) {
for (MessageSeveritySetting ms : ValidatorExtensionReader.getDefault().addMessages(this)){
settings.put(ms.getId(), ms);
}
}
public abstract String getName();
/**
* Answer the project that you were enabled on.
*
* @return null if you are a global (i.e. workspace level) validator.
*/
public IProject getProject(){
return _project;
}
/**
* Answer the name of the class that implements the validator.
*/
public abstract String getValidatorClassname();
/**
* Is this validator currently enabled for validations that are triggered manually?
*/
public boolean isManualValidation() {
return _manualValidation;
}
/**
* Set whether this validator should be triggered as part of a manual validation.
*
* @param manualValidation
* @return true if the setting changed.
*/
public boolean setManualValidation(boolean manualValidation) {
return setManualValidation2(manualValidation);
}
protected final boolean setManualValidation2(boolean manualValidation) {
boolean changed = false;
if (_manualValidation != manualValidation){
bumpChangeCountGlobal();
changed = true;
_manualValidation = manualValidation;
}
return changed;
}
/**
* Is this validator currently enabled for validations that are triggered by builds?
*/
public boolean isBuildValidation() {
return _buildValidation;
}
/**
* Has the validator changed since it was last created or copied? Or was it migrated from an earlier version.
*/
public boolean isChanged(){
if (_changeCountGlobal > 0 || _changeCountMessages > 0 || _migrated)return true;
return false;
}
/**
* Has the validator's implementation been loaded yet? This is used by some test cases to ensure that
* plug-ins are not loaded too early.
*/
abstract boolean isLoaded();
/**
* Set whether this validator should be triggered by the build process.
*
* @param buildValidation
* @return true if the setting changed.
*/
public boolean setBuildValidation(boolean buildValidation) {
return setBuildValidation2(buildValidation);
}
protected final boolean setBuildValidation2(boolean buildValidation) {
boolean changed = false;
if (_buildValidation != buildValidation){
bumpChangeCountGlobal();
changed = true;
_buildValidation = buildValidation;
}
return changed;
}
/**
* Get the id of the "real" validator, that is the validator that will be called when this delegating
* validator is asked to validate something. If this isn't a delegating validator answer null.
*/
public String getDelegatingId() {
return _delegatingId;
}
/**
* Set the id of the "real" validator, that is the validator that will be called when this delegating
* validator is asked to validate something.
*
* @param delegating the id of the validator that is actually going to perform the validation.
*/
public void setDelegatingId(String delegating) {
if (!Misc.same(_delegatingId, delegating)){
_delegatingId = delegating;
bumpChangeCountGlobal();
}
}
public int getVersion() {
return _version;
}
public void setVersion(int version) {
if (_version != version){
_version = version;
bumpChangeCountGlobal();
}
}
@Override
public String toString() {
return getName();
}
/**
* A validator that uses version 1 of the validation framework.
* @author karasiuk
*
*/
public static class V1 extends Validator {
private ValidatorMetaData _vmd;
/**
* Create a new version 1 validator.
* @param vmd
* @param config this is used to set the global enablement options. In some case this can be null.
*/
public V1(ValidatorMetaData vmd, ValidationConfiguration config, IProject project){
super(project);
_vmd = vmd;
if (config != null){
setBuildValidation(config.isBuildEnabled(vmd));
setManualValidation(config.isManualEnabled(vmd));
}
setDelegatingId(ValidatorDelegatesRegistry.getInstance().getDefaultDelegate(_vmd.getValidatorUniqueName()));
if (_vmd.getMarkerIds() != null && _vmd.getMarkerIds().length > 0)setMarkerId(_vmd.getMarkerIds()[0]);
resetChangeCounters();
}
@Override
public void validationFinishing(IProject project, ValidationState state, IProgressMonitor monitor) {
if (project != null) {
IValidator v = asIValidator();
if (v instanceof IValidatorExtender) {
IValidatorExtender vExt = (IValidatorExtender) v;
vExt.validationFinishing(project, state, monitor);
}
}
}
@Override
public IValidator asIValidator() {
IValidator v = null;
try {
v = _vmd.getValidator();
}
catch (InstantiationException e){
ValidationPlugin.getPlugin().handleException(e);
return null;
}
return v;
}
public V1 asV1Validator() {
return this;
}
public Validator copy(boolean includeChangeCounts) {
V1 v = new V1Copy(_vmd, null, _project);
v.copyLocal(this, includeChangeCounts);
return v;
}
public String getName() {
return _vmd.getValidatorDisplayName();
}
public ValidatorMetaData getVmd(){
return _vmd;
}
public String getValidatorClassname(){
String name = ""; //$NON-NLS-1$
try {
name = _vmd.getValidator().getClass().getName();
}
catch (Exception e){
// eat it
}
return name;
}
public String getId() {
return _vmd.getValidatorUniqueName();
}
@Override
boolean isLoaded() {
return _vmd.isActive();
}
@Override
public boolean setBuildValidation(boolean buildValidation) {
boolean changed = super.setBuildValidation(buildValidation);
_vmd.setBuildValidation(buildValidation);
return changed;
}
@Override
public boolean setManualValidation(boolean manualValidation) {
boolean changed = super.setManualValidation(manualValidation);
_vmd.setManualValidation(manualValidation);
return changed;
}
@Override
protected boolean shouldValidate(IResource resource, ContentTypeWrapper contentTypeWrapper) {
return _vmd.isApplicableTo(resource);
}
@Override
protected boolean shouldValidateProject(IProject project) {
// TODO determine if this can be optimized
return true;
}
@Override
public ValidationResult validate(IResource resource, int kind, ValOperation operation,
IProgressMonitor monitor) {
if (monitor == null)monitor = new NullProgressMonitor();
ValidationResult vr = new ValidationResult();
IValidator v = asIValidator();
if (v == null)return null;
if (shouldSkipValidator(resource, operation))return null;
try {
IProject project = resource.getProject();
SummaryReporter reporter = new SummaryReporter(project, monitor);
IWorkbenchContext helper = _vmd.getHelper(project);
if (helper instanceof WorkbenchContext){
WorkbenchContext wc = (WorkbenchContext)helper;
List<String> files = new LinkedList<String>();
// [213631] The JSP validator expects full paths not relative paths, but the XML validator
// expects relative paths.
files.add(wc.getPortableName(resource));
wc.setValidationFileURIs(files);
}
try {
ValidatorLauncher.getLauncher().start(helper, v, reporter);
}
finally {
try {
v.cleanup(reporter);
}
finally {
helper.cleanup(reporter);
}
}
vr.incrementError(reporter.getSeverityHigh());
vr.incrementWarning(reporter.getSeverityNormal());
vr.incrementInfo(reporter.getSeverityLow());
}
catch (Exception e){
ValidationPlugin.getPlugin().handleException(e);
}
return vr;
}
private static final String VALIDATE_PROJECT_ONCE = "ValidateProjectOnce"; //$NON-NLS-1$
@SuppressWarnings("unchecked")
private boolean shouldSkipValidator(IResource resource, ValOperation operation) {
if (_vmd.isValidateByProject())
{
ValidationState validationState = operation.getState();
Set<String> projectsNameSet = (Set<String>)validationState.get(VALIDATE_PROJECT_ONCE);
String projectName = resource.getProject().getName();
if (projectsNameSet == null) {
projectsNameSet = new HashSet<String>();
validationState.put(VALIDATE_PROJECT_ONCE, projectsNameSet);
}
if (projectsNameSet.contains(projectName))return true;
else projectsNameSet.add(projectName);
}
return false;
}
/*
* GRK - Because I didn't want to try to make a true copy of the V1 validator, (because I didn't
* want to copy the vmd object), I came up with this approach to only copy the fields that
* the preference page was worried about.
*/
public final static class V1Copy extends V1 {
public V1Copy(ValidatorMetaData vmd, ValidationConfiguration vc, IProject project){
super(vmd, vc, project);
}
@Override
public boolean setManualValidation(boolean bool) {
return setManualValidation2(bool);
}
@Override
public boolean setBuildValidation(boolean bool) {
return setBuildValidation2(bool);
}
}
}
/**
* A validator that uses version 2 of the validation framework.
* @author karasiuk
*
*/
public final static class V2 extends Validator implements IAdaptable {
private AbstractValidator _validator;
private List<FilterGroup> _groups = new LinkedList<FilterGroup>();
private FilterGroup[] _groupsArray;
/** The full id of the extension. */
private String _id;
/** Name of the validator. */
private String _name;
/**
* We don't want to create the validator too early, as it may trigger new plug-ins to be loaded.
* We delay that as long as possible, by starting with just the config element.
*/
private IConfigurationElement _validatorConfigElement;
private String _validatorClassName;
/**
* An array containing the validator group IDs for which this validator is a member.
*/
private String[] _validatorGroupIds;
/**
* If this validator is a delegating validator, then this is the "real" validator (i.e. the one that
* does the work).
*/
private AbstractValidator _delegated;
/** How many times has a group field in this validator been changed since it was created (or copied)? */
protected transient int _changeCountGroups;
private Level _level;
/**
* Do we still need to invoke the validateStarting method for this validator, for the null project?
*
* Because we do not want to activate a validator's plug-in too soon, we do not activate the validator
* as a reaction to the global validation starting event. Instead we mark it pending, and wait until
* we are sure that we have something to validate.
*
* If this flag is true, it means that the validateStarting method still needs to be called for this validator.
*/
private AtomicBoolean _pendingValidationStarted = new AtomicBoolean();
V2(IConfigurationElement configElement, IProject project){
super(project);
assert configElement != null;
_validatorConfigElement = configElement;
_validatorClassName = configElement.getAttribute(ExtensionConstants.AttribClass);
IConfigurationElement[] groupReferenceElements = configElement.getChildren(ExtensionConstants.Group.elementGroup);
List<String> validatorGroupIDs = new ArrayList<String>();
for (IConfigurationElement groupElement : groupReferenceElements) {
String id = groupElement.getAttribute(ExtensionConstants.Group.attId);
if (id != null)validatorGroupIDs.add(id);
}
_validatorGroupIds = validatorGroupIDs.toArray(new String[validatorGroupIDs.size()]);
init();
}
private V2(IProject project, String validatorClassName, AbstractValidator validator){
super(project);
assert validator != null;
_validatorClassName = validatorClassName;
_validator = validator;
init();
}
private void init(){
try {
String id = ConfigurationManager.getManager().getConfiguration(_project).getDelegateForTarget(_validatorClassName);
if (id == null) id = ValidatorDelegatesRegistry.getInstance().getDefaultDelegate(_validatorClassName);
setDelegatingId(id);
}
catch (InvocationTargetException e){
ValidationPlugin.getPlugin().handleException(e);
}
resetChangeCounters();
}
public synchronized void add(FilterGroup fg) {
assert fg != null;
_groupsArray = null;
_groups.add(fg);
bumpChangeCountGroups();
}
@Override
public IValidator asIValidator() {
AbstractValidator av = getDelegatedValidator();
if (av instanceof IValidator)return (IValidator)av;
return super.asIValidator();
}
@Override
public V2 asV2Validator() {
return this;
}
/**
* Let the validator know that a clean is about to happen.
*
* @param project the project that is being cleaned. This can be null which means that the workspace
* is being cleaned (in which case a separate call will be made for each open project).
*
*/
@Override
public void clean(IProject project, ValOperation operation, IProgressMonitor monitor) {
getDelegatedValidator().clean(project, operation.getState(), monitor);
}
@Override
public Validator copy(boolean includeChangeCounts) {
V2 v = null;
if (_validatorConfigElement != null)v = new V2(_validatorConfigElement, _project);
else v = new V2(_project, _validatorClassName, _validator);
v.copyLocal(this, includeChangeCounts);
if (includeChangeCounts)v._changeCountGroups = _changeCountGroups;
FilterGroup[] groups = getGroups();
v._groupsArray = new FilterGroup[groups.length];
for (int i=0; i<groups.length; i++){
v._groupsArray[i] = groups[i];
v._groups.add(groups[i]);
}
v._id = _id;
v._name = _name;
v._validatorGroupIds = _validatorGroupIds;
v._pendingValidationStarted = _pendingValidationStarted;
return v;
}
public int getChangeCountGroups(){
return _changeCountGroups;
}
public void bumpChangeCountGroups(){
_changeCountGroups++;
}
public Level getLevel() {
return _level;
}
public void setLevel(Level level) {
assert _level == null;
_level = level;
}
/**
* Answer the actual validator that is going to perform the validation. If this is a normal validator this
* method will simply answer itself. However if this is a delegating validator, then this will answer the
* "real" validator.
*/
public AbstractValidator getDelegatedValidator(){
AbstractValidator delegated = _delegated;
if (delegated != null)return delegated;
else if (getDelegatingId() == null)return getValidator();
try {
ValidatorDelegateDescriptor vdd = ValidatorDelegatesRegistry.getInstance()
.getDescriptor(getValidatorClassname(), getDelegatingId());
if (vdd == null)return getValidator();
delegated = vdd.getValidator2();
}
catch (Exception e){
ValidationPlugin.getPlugin().handleException(e);
delegated = new NullValidator();
}
delegated.setParent(this);
_delegated = delegated;
return delegated;
}
@Override
public String getId() {
return _id;
}
/**
* Answer the validator's filter groups.
* @return an empty array if the validator does not have any filter groups.
*/
public synchronized FilterGroup[] getGroups(){
FilterGroup[] groups = _groupsArray;
if (groups == null){
groups = new FilterGroup[_groups.size()];
_groups.toArray(groups);
_groupsArray = groups;
}
return groups;
}
@Override
public String getName() {
return _name;
}
public AbstractValidator getValidator() {
if (_validator == null){
try {
_validator = (AbstractValidator)_validatorConfigElement.createExecutableExtension(ExtensionConstants.AttribClass);
}
catch (Exception e){
ValidationPlugin.getPlugin().handleException(e);
IContributor contrib = _validatorConfigElement.getContributor();
String message = NLS.bind(ValMessages.ErrConfig, contrib.getName());
ValidationPlugin.getPlugin().logMessage(IStatus.ERROR, message);
_validator = new NullValidator();
}
_validator.setParent(this);
_validatorConfigElement = null;
}
return _validator;
}
@Override
public String getValidatorClassname(){
return _validatorClassName;
}
public String[] getValidatorGroups(){
return _validatorGroupIds;
}
@Override
public int hashCodeForConfig() {
int h = super.hashCodeForConfig();
if (_id != null)h += _id.hashCode();
if (_groups != null){
for (FilterGroup fg : _groups)h += fg.hashCodeForConfig();
}
return h;
}
@Override
public boolean isChanged() {
if (_changeCountGroups > 0)return true;
return super.isChanged();
}
@Override
boolean isLoaded() {
return _validator != null;
}
@Override
public boolean shouldClearMarkers(ValidationEvent event) {
return getValidator().shouldClearMarkers(event);
}
/**
* Answer true if this validator, based on it's filters, should validate this resource.
*
* @return true if the resource should be validated.
*/
@Override
protected boolean shouldValidate(IResource resource, ContentTypeWrapper contentTypeWrapper) {
FilterGroup[] groups = getGroups();
IProject project = resource.getProject();
for (FilterGroup group : groups){
if (!group.shouldValidate(project, resource, contentTypeWrapper))return false;
}
return true;
}
@Override
public void setDelegatingId(String delegating) {
super.setDelegatingId(delegating);
_delegated = null;
}
public synchronized void setGroups(List<FilterGroup> groups){
_groups = groups;
_groupsArray = null;
bumpChangeCountGroups();
}
public void setId(String id) {
if (!Misc.same(_id, id)){
_id = id;
bumpChangeCountGlobal();
}
}
public void setName(String name) {
if (!Misc.same(_name, name)){
_name = name;
bumpChangeCountGlobal();
}
}
@Override
public ValidationResult validate(IResource resource, int kind, ValOperation operation, IProgressMonitor monitor){
return validate(resource, kind, operation, monitor, null);
}
@Override
public ValidationResult validate(IResource resource, int kind, ValOperation operation, IProgressMonitor monitor, ValidationEvent event) {
ValidationResult vr = null;
if (operation == null)operation = new ValOperation();
if (monitor == null)monitor = new NullProgressMonitor();
try {
if (event == null)event = new ValidationEvent(resource, kind, null);
vr = getDelegatedValidator().validate(event, operation.getState(), monitor);
if (vr == null)vr = getDelegatedValidator().validate(resource, kind, operation.getState(), monitor);
}
catch (Exception e){
try {
String msg = NLS.bind(ValMessages.LogValEnd, getName(), resource.getLocationURI());
ValidationPlugin.getPlugin().logMessage(IStatus.ERROR, msg);
}
catch (Exception e2 ){
// ignore it
}
ValidationPlugin.getPlugin().handleException(e);
}
if (vr != null){
if (vr.getValidationException() != null){
ValidationPlugin.getPlugin().handleException(vr.getValidationException());
}
updateResults(vr);
if (vr.getDependsOn() != null){
ValidationFramework.getDefault().getDependencyIndex().set(getId(), resource, vr.getDependsOn());
}
IResource[] validated = vr.getValidated();
if (validated != null){
for (int i=0; i<validated.length; i++){
operation.addValidated(getId(), validated[i]);
}
}
ValidatorMessage[] msgs = vr.getMessages();
// if (sanityTest(msgs.length, resource)){
MarkerManager mm = MarkerManager.getDefault();
for (ValidatorMessage m : msgs){
mm.createMarker(m, getId());
}
// }
// else {
// setBuildValidation(false);
// setManualValidation(false);
// }
}
return vr;
}
/**
* Perform a simple sanity test to ensure that the validator is configured correctly.
* @param numberofMessages number of messages that the validator produced.
* @return true if the test passed
*/
// private boolean sanityTest(int numberofMessages, IResource resource) {
// if (numberofMessages < 201)return true;
//
// String resName = ""; //$NON-NLS-1$
// if (resource != null)resName = resource.getName();
// String message = NLS.bind(ValMessages.ConfigError, new Object[]{
// getName(), getId(), String.valueOf(numberofMessages), resName});
// ValidationPlugin.getPlugin().logMessage(IStatus.ERROR, message);
//
// return false;
// }
/**
* If the validator is using a report helper then update it with any of the messages that were
* added directly to the validation result.
* @param vr
*/
private void updateResults(ValidationResult vr) {
ReporterHelper rh = vr.getReporterHelper();
if (rh == null)return;
ClassLoader classloader = getDelegatedValidator().getClass().getClassLoader();
for (IMessage message : rh.getMessages()){
Object target = message.getTargetObject();
if (target != null){
IResource res = null;
if (target instanceof IResource)res = (IResource)target;
if (res == null){
target = message.getAttribute(IMessage.TargetResource);
if (target != null && target instanceof IResource)res = (IResource)target;
}
if (res != null){
ValidatorMessage vm = ValidatorMessage.create(message.getText(classloader), res);
if (getMarkerId() != null)vm.setType(getMarkerId());
vr.add(vm);
int markerSeverity = IMarker.SEVERITY_INFO;
int sev = message.getSeverity();
if ((sev & IMessage.HIGH_SEVERITY) != 0)markerSeverity = IMarker.SEVERITY_ERROR;
else if ((sev & IMessage.NORMAL_SEVERITY) != 0)markerSeverity = IMarker.SEVERITY_WARNING;
vm.setAttribute(IMarker.SEVERITY, markerSeverity);
vm.setAttribute(IMarker.LINE_NUMBER, message.getLineNumber());
int offset = message.getOffset();
if (offset != IMessage.OFFSET_UNSET){
vm.setAttribute(IMarker.CHAR_START, offset);
int len = message.getLength();
if (len != IMessage.OFFSET_UNSET){
vm.setAttribute(IMarker.CHAR_START, offset);
vm.setAttribute(IMarker.CHAR_END, offset+len);
}
}
String groupName = message.getGroupName();
if (groupName != null){
vm.setAttribute(ConfigurationConstants.VALIDATION_MARKER_GROUP, groupName);
}
copyAttributes(message, vm);
}
}
}
}
@SuppressWarnings("unchecked")
private void copyAttributes(IMessage message, ValidatorMessage vm) {
// I made this a separate method, so that I could localize the suppression of unchecked warnings.
Map attributes = message.getAttributes();
if (attributes != null){
for (Iterator it = attributes.entrySet().iterator(); it.hasNext();){
Map.Entry me = (Map.Entry)it.next();
String key = (String)me.getKey();
vm.setAttribute(key, me.getValue());
}
}
}
@Override
public void validationStarting(IProject project, ValidationState state, IProgressMonitor monitor) {
if (project == null)_pendingValidationStarted.set(true);
else {
AbstractValidator val = getDelegatedValidator();
if (_pendingValidationStarted.getAndSet(false)){
val.validationStarting(null, state, monitor);
}
val.validationStarting(project, state, monitor);
}
}
@Override
public void validationFinishing(IProject project, ValidationState state, IProgressMonitor monitor) {
if (project == null){
if (!_pendingValidationStarted.getAndSet(false))getDelegatedValidator().validationFinishing(null, state, monitor);
}
else getDelegatedValidator().validationFinishing(project, state, monitor);
}
@SuppressWarnings("unchecked")
public Object getAdapter(Class adapter) {
return Platform.getAdapterManager().getAdapter(this, adapter);
}
public synchronized void remove(FilterGroup group) {
_groups.remove(group);
_groupsArray = null;
bumpChangeCountGroups();
}
@Override
public void resetChangeCounters() {
super.resetChangeCounters();
_changeCountGroups = 0;
}
@Override
protected boolean shouldValidateProject(IProject project) {
FilterGroup[] groups = getGroups();
ContentTypeWrapper ctw = new ContentTypeWrapper();
for (FilterGroup group : groups){
if (!group.shouldValidate(project, null, ctw))return false;
}
return true;
}
public synchronized void replaceFilterGroup(FilterGroup existing, FilterGroup merged) {
remove(existing);
add(merged);
}
}
public String getSourceId() {
return _sourceId;
}
public void setSourceId(String sourceId) {
if (!Misc.same(_sourceId, sourceId)){
_sourceId = sourceId;
bumpChangeCountGlobal();
}
}
void setMessages(Map<String, MessageSeveritySetting> map) {
_messageSettings = map;
bumpChangeCountMessages();
}
public int getChangeCountGlobal() {
return _changeCountGlobal;
}
public boolean hasGlobalChanges(){
return _migrated || _changeCountGlobal > 0;
}
public int getChangeCountMessages() {
return _changeCountMessages;
}
public void bumpChangeCountMessages(){
_changeCountMessages++;
}
public void resetChangeCounters() {
_changeCountGlobal = 0;
_changeCountMessages = 0;
}
public void bumpChangeCountGlobal(){
_changeCountGlobal++;
}
/**
* Answer true if you have the same configuration settings as validator.
* @param validator this can be null.
*/
public boolean sameConfig(Validator validator) {
if (validator == null)return false;
return hashCodeForConfig() == validator.hashCodeForConfig();
}
public String getMarkerId() {
return _markerId;
}
public void setMarkerId(String markerId) {
_markerId = markerId;
if (markerId != null)MarkerManager.getDefault().getMarkers().add(markerId);
}
}