/*******************************************************************************
 * Copyright (c) 2007, 2017 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * 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();
	public abstract String getDescription();

	/**
	 * 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 String getDescription() {
		return null;
	}

	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;
	/** Longer description of the validator. */
	private String			_description;
	
	/** 
	 * 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._description = _description;
		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 String getDescription() {
		return _description;
	}

	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) {
	    if (resource.isDerived()){
	      return false;
	    }
		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();
		}
	}

	public void setDescription(String description) {
		if (!Misc.same(_description, description)){
			_description = description;
			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);
}

}
