/*******************************************************************************
 * Copyright (c) 2007, 2011 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.internal;

import java.lang.reflect.InvocationTargetException;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.wst.common.project.facet.core.FacetedProjectFramework;
import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectEvent;
import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectListener;
import org.eclipse.wst.validation.Friend;
import org.eclipse.wst.validation.IPerformanceMonitor;
import org.eclipse.wst.validation.IValidatorGroupListener;
import org.eclipse.wst.validation.PerformanceCounters;
import org.eclipse.wst.validation.ValidationEvent;
import org.eclipse.wst.validation.ValidationFramework;
import org.eclipse.wst.validation.ValidationResult;
import org.eclipse.wst.validation.ValidationState;
import org.eclipse.wst.validation.Validator;
import org.eclipse.wst.validation.internal.model.GlobalPreferences;
import org.eclipse.wst.validation.internal.model.GlobalPreferencesValues;
import org.eclipse.wst.validation.internal.model.IValidatorVisitor;
import org.eclipse.wst.validation.internal.model.ProjectPreferences;
import org.eclipse.wst.validation.internal.operations.ManualValidatorsOperation;
import org.eclipse.wst.validation.internal.plugin.ValidationPlugin;
import org.osgi.service.prefs.BackingStoreException;

/**
 * A central place to keep track of all the validators.
 * @author karasiuk
 *
 */
public final class ValManager implements IValChangedListener, IFacetedProjectListener, IProjectChangeListener {
	
	/**
	 * Projects may be allowed to override the global validation settings. If that is the case then those
	 * project specific settings are saved here. If the key exists, but the value is null, then that
	 * means that the project has been checked and it does not have any specific settings.
	 */
	private final Map<IProject, ProjectPreferences> _projectPreferences = 
		Collections.synchronizedMap(new HashMap<IProject, ProjectPreferences>(50));
	
	private final AtomicReference<GlobalPreferences> _globalPreferences = new AtomicReference<GlobalPreferences>();
		
	/**
	 * This number increases each time any of the validation configurations change. It is used to determine
	 * if information that we have cached in the ValProperty is stale or not. This starts off at zero, each time
	 * the workbench is started.
	 */
	private final AtomicInteger _configNumber = new AtomicInteger();
	
	private final ValidatorIdManager _idManager = new ValidatorIdManager();
	private final ValidatorCache 	_cache = new ValidatorCache();
		
	private static final QualifiedName StatusBuild = new QualifiedName(ValidationPlugin.PLUGIN_ID, "sb"); //$NON-NLS-1$
	private static final QualifiedName StatusManual = new QualifiedName(ValidationPlugin.PLUGIN_ID, "sm"); //$NON-NLS-1$

	
	public static ValManager getDefault(){
		return Singleton.valManager;
	}
	
	private ValManager(){
		ValPrefManagerGlobal.getDefault().addListener(this);
		ValPrefManagerProject.addListener(this);
		FacetedProjectFramework.addListener(this, IFacetedProjectEvent.Type.PROJECT_MODIFIED);
		EventManager.getManager().addProjectChangeListener(this);
	}
	
	/**
	 * This needs to be called if the ValManager is ever deleted.
	 */
	public void dispose(){
		// currently nobody calls this method, because this instance is never removed, but the method is
		// here for completeness none the less.
		ValPrefManagerGlobal.getDefault().removeListener(this);
		ValPrefManagerProject.removeListener(this);
		FacetedProjectFramework.removeListener(this);
		EventManager.getManager().removeProjectChangeListener(this);	
	}
	
	/**
	 * Answer all the registered validators. If you are planning on making changes to the validators,
	 * and then saving them in a preference store then you probably want the getValidatorsCopy method.
	 * Because if you make changes to the original validators, and since we only save differences,
	 * there won't be any differences. 
	 * 
	 * @return Answer the validators in name sorted order. Answer an empty array if there are no validators.
	 * 
	 * @see #getValidatorsCopy()
	 */
	public Validator[] getValidators(){
		return getValidators(null);
	}
	
	/**
	 * Answer copies of all the registered validators. If you are going to be making changes to the validators
	 * and then saving them backing into the preference store, then this is the method to use.
	 * 
	 * @return Answer an empty array if there are no validators.
	 */
	public Validator[] getValidatorsCopy(){
		Validator[] orig = getValidators();
		Validator[] copy = new Validator[orig.length];
		for (int i=0; i<orig.length; i++)copy[i] = orig[i].copy();
		return copy;
	}
	
	/**
	 * Answer all the validators for the given project.
	 * <p>
	 * Individual projects may override the global validation preference
	 * settings. If the project has it's own settings, then those validators are
	 * returned via this method.
	 * </p>
	 * <p>
	 * The following approach is used. For version 1 validators, the validator
	 * is only returned if it is defined to operate on this project type. This
	 * is the way that the previous version of the framework did it. For version
	 * 2 validators, they are all returned.
	 * </p>
	 * 
	 * @param project
	 *            this may be null, in which case the global preferences are
	 *            returned.
	 * @param respectOverrideSettings
	 *            if this is true then the validators that get returned are
	 *            based on the override settings. So for example, if the global
	 *            preferences do not allow project overrides then none of the
	 *            project settings are used. Normal validation would set this to true.
	 *            The properties page would set this to false.
	 *            
	 * @deprecated Use {@link #getValidatorsNotCached(IProject)} instead            
	 */
	public Validator[] getValidators(IProject project, boolean respectOverrideSettings) throws ProjectUnavailableError {
		return getValidators(project);
	}
	
	/**
	 * Answer a cached copy of the the validators for a given project. This is a front end method, 
	 * for the getValidatorsNotCached() method.
	 * <p>
	 * Individual projects may override the global validation preference
	 * settings. If the project has it's own settings, then those validators are
	 * returned via this method.
	 * </p>
	 * <p>
	 * The following approach is used. For version 1 validators, the validator
	 * is only returned if it is defined to operate on this project type. This
	 * is the way that the previous version of the framework did it. For version
	 * 2 validators, they are all returned.
	 * </p>
	 * 
	 * @param project
	 *            This may be null, in which case the global preferences are
	 *            returned.
	 * 
	 * @return The validators in name sorted order.
	 */
	public Validator[] getValidators(IProject project) throws ProjectUnavailableError {
		return _cache.getValidatorsCached(project);
	}
	
	/**
	 * Answer all the validators for the given project.
	 * <p>
	 * Individual projects may override the global validation preference
	 * settings. If the project has it's own settings, then those validators are
	 * returned via this method.
	 * </p>
	 * <p>
	 * The following approach is used. For version 1 validators, the validator
	 * is only returned if it is defined to operate on this project type. This
	 * is the way that the previous version of the framework did it. For version
	 * 2 validators, they are all returned.
	 * </p>
	 * 
	 * @param project
	 *            This may be null, in which case the global preferences are
	 *            returned.
	 * 
	 * @return The validators in name sorted order.
	 */
	private Validator[] getValidatorsNotCached(IProject project) throws ProjectUnavailableError {
		Map<String,Validator> v2Vals = getV2Validators(project, UseProjectPreferences.Normal);
		TreeSet<Validator> sorted = new TreeSet<Validator>();
		sorted.addAll(v2Vals.values());
		
		try {
			ValidationConfiguration vc = ConfigurationManager.getManager().getConfiguration(project);
			if (project == null){
				// If the project is null we need to use this approach, since you can not use new ManualValidatorsOperation(null)
				ValidatorMetaData[] vmds = vc.getValidators();
				for (ValidatorMetaData vmd : vmds){
					Validator v = Validator.create(vmd, vc, project);
					sorted.add(v);
				}
			}
			else {
				ManualValidatorsOperation mvo = new ManualValidatorsOperation(project);
				Set<ValidatorMetaData> vmds = mvo.getEnabledValidators();
				for (ValidatorMetaData vmd : vmds){
					Validator v = Validator.create(vmd, vc, project);
					sorted.add(v);
				}
			}
		}
		catch (InvocationTargetException e){
			ValidationPlugin.getPlugin().handleException(e);
		}
		
		Validator[] vals = new Validator[sorted.size()];
		sorted.toArray(vals);
		return vals;
	}
	/**
	 * Validators can use project level settings (Project natures and facets) to
	 * determine if they are applicable to the project or not.
	 * 
	 * @param project
	 *            The project that the configuration is based on.
	 * @param mustUseProjectSettings
	 *            Force the project properties to be used. There is a case where the user has toggled the
	 *            Enable project specific settings checkbox in the dialog, but has not yet committed the
	 *            changes. This allows that setting to be passed through.
	 * @return The validators that are configured to run on this project based
	 *         on the project level settings. These are the "live" validators, they are not copies.
	 * @throws ProjectUnavailableError
	 * 
	 * @deprecated Use getValidatorsConfiguredForProject(IProject project, UseProjectPreferences useProject)
	 */
	public Validator[] getValidatorsConfiguredForProject(IProject project, boolean mustUseProjectSettings) throws ProjectUnavailableError {
		UseProjectPreferences useProject = UseProjectPreferences.Normal;
		return getValidatorsConfiguredForProject(project, useProject);
	}
	
	/**
	 * Validators can use project level settings (Project natures and facets) to
	 * determine if they are applicable to the project or not.
	 * 
	 * @param project
	 *            The project that the configuration is based on.
	 * @param useProject
	 *            Specifies how to use the project preferences. This can be used
	 *            to force the project properties to be used. There is a case
	 *            where the user has toggled the Enable project specific
	 *            settings checkbox in the dialog, but has not yet committed the
	 *            changes. This allows that setting to be passed through.
	 * @return The validators that are configured to run on this project based
	 *         on the project level settings. These are the "live" validators,
	 *         they are not copies.
	 * @throws ProjectUnavailableError
	 */
	public Validator[] getValidatorsConfiguredForProject(IProject project, UseProjectPreferences useProject) throws ProjectUnavailableError {
		Map<String,Validator> v2Vals = getV2Validators(project, useProject);
		TreeSet<Validator> sorted = new TreeSet<Validator>();
		sorted.addAll(v2Vals.values());
		
		if (useProject == UseProjectPreferences.MustNotUse){
			sorted.addAll(ExtensionValidators.instance().getV1Validators(project));
		}
		else {
			try {
				ValidationConfiguration vc = ConfigurationManager.getManager().getProjectConfiguration(project);
				ValidatorMetaData[] vmds = vc.getValidators();
				for (ValidatorMetaData vmd : vmds) {
					Validator v = Validator.create(vmd, vc, project);
					sorted.add(v);
				}
			}
			catch (InvocationTargetException e){
				ValidationPlugin.getPlugin().handleException(e);
			}
		}
				
		List<Validator> list = new LinkedList<Validator>();
		for (Validator v : sorted){
			if (v.shouldValidateProject(project, false, false))list.add(v);
		}
		
		Validator[]vals = new Validator[list.size()];
		list.toArray(vals);
		return vals;
	}
	
	/**
	 * Answer the V2 validators that are in effect for this project. The
	 * following approach is used:
	 * <ol>
	 * <li>The validators that are defined by the extension point are loaded.</li>
	 * <li>They are customized by any global preferences.</li>
	 * <li>If project customizations are allowed, they are customized by the
	 * project preferences.
	 * </ol>
	 * 
	 * @param project
	 *            This may be null, in which case only the global preferences
	 *            are used.
	 * @param useProject
	 *            Specifies how to use the project preferences. This can be used
	 *            to force the project properties to be used. There is a case
	 *            where the user has toggled the Enable project specific
	 *            settings checkbox in the dialog, but has not yet committed the
	 *            changes. This allows that setting to be passed through.
	 *            
	 * @return
	 */
	private Map<String,Validator> getV2Validators(IProject project, UseProjectPreferences useProject){
		Map<String,Validator> extVals = ExtensionValidators.instance().getMapV2Copy();
		try {
			List<Validator> vals = ValPrefManagerGlobal.getDefault().getValidators();
			for (Validator v : vals)extVals.put(v.getId(), v);
			
			if (useProject != UseProjectPreferences.MustNotUse){
				if (useProject == UseProjectPreferences.MustUse || !mustUseGlobalValidators(project)){
					//TODO should probably cache this vpm
					ValPrefManagerProject vpm = new ValPrefManagerProject(project);
					vals = vpm.getValidators(extVals);
					for (Validator v : vals)extVals.put(v.getId(), v);
					
					for (Validator v : getProjectPreferences(project).getValidators())extVals.put(v.getId(), v);
				}	
			}
		}
		catch (BackingStoreException e){
			ValidationPlugin.getPlugin().handleException(e);
		}
		return extVals;
	}
	

	/**
	 * Answer true if we must use the global settings for this project. If the global preferences do not
	 * allow overrides, or if this project does not allow overrides then the global preferences must be used.
	 *  
	 * @param project project that is being tested. It can be null, in which case the global preferences must be used.
	 * @return true if the global preferences must be used.
	 */
	public boolean mustUseGlobalValidators(IProject project){
		if (project == null)return true;
		if (!getGlobalPreferences().getOverride())return true;
		ProjectPreferences pp = _projectPreferences.get(project);
		if (pp != null)return !pp.getOverride();
		
		ValPrefManagerProject vpm = new ValPrefManagerProject(project);
		return !vpm.getOverride();
	}
	
	/**
	 * Answer the validator with the given id that is in effect for the given project.
	 * 
	 * @param id The validator id.
	 * @param project
	 * @return null if the validator is not found
	 */
	public Validator getValidator(String id, IProject project){
		Validator[] vals = getValidators(project);
		for (Validator v : vals){
			if (v.getId().equals(id))return v;
		}
		return null;
	}
	
	/**
	 * @see ValidationFramework#getValidator(String, IProject)
	 */
	public Validator getValidatorWithId(String id, IProject project){
		Validator[] vals = getValidators(project);
		for (Validator v : vals){
			if (v.getId().equals(id))return v;
		}
		return null;
	}
					
	/**
	 * Answer true if the resource has any enabled validators.
	 * 
	 * @param resource a file, folder or project.
	 * 
	 * @param isManual if true then the validator must be turned on for manual validation. 
	 * If false then the isManualValidation setting isn't used to filter out validators.
	 *   
	 * @param isBuild if true then the validator must be turned on for build based validation.
	 * If false then the isBuildValidation setting isn't used to filter out validators.  
	 */
	public boolean hasValidators(IResource resource, boolean isManual, boolean isBuild){
		if (resource instanceof IProject){
			IProject project = (IProject)resource;
			return ValManager.getDefault().getValidators(project).length > 0;
		}
		else if (resource instanceof IFolder){
			IFolder folder = (IFolder)resource;
			HasValidatorVisitor v = new HasValidatorVisitor(isManual, isBuild);
			return v.hasValidator(folder);
		}
		else {
			ContentTypeWrapper ctw = new ContentTypeWrapper();
			for (Validator val : ValManager.getDefault().getValidators(resource.getProject())){
				if (Friend.shouldValidate(val, resource, isManual, isBuild, ctw))return true;
			}			
		}
		return false;
	}
	
	/**
	 * Answer true if the project has disabled all of it's validators, or if project overrides are not
	 * allowed if global validation has been disabled.
	 * 
	 * @param project the project that is being consulted, or null if only the global settings are to be 
	 * checked.
	 */
	public boolean isDisabled(IProject project){
		GlobalPreferences gp = getGlobalPreferences();
		if (!gp.getOverride() || project == null)return gp.getDisableAllValidation();
		
		ProjectPreferences pp = _projectPreferences.get(project);
		if (pp == null)return gp.getDisableAllValidation();
		return pp.getSuspend();		
	}
			
	/**
	 * Answer all the registered validators as they were defined by the extension points. That is
	 * answer the validators as if the user has never applied any customizations.
	 * 
	 * @return Answer an empty array if there are no validators.
	 */
	public static Validator[] getDefaultValidators() throws InvocationTargetException {
		Map<String,Validator> extVals = ExtensionValidators.instance().getMapV2();
		TreeSet<Validator> sorted = new TreeSet<Validator>();
		for (Validator v : extVals.values())sorted.add(v);
		
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		GlobalConfiguration gc = new GlobalConfiguration(root);
		gc.resetToDefault();
		for (ValidatorMetaData vmd : gc.getValidators()){
			Validator v = Validator.create(vmd, gc, null);
			v.setBuildValidation(vmd.isBuildValidation());
			v.setManualValidation(vmd.isManualValidation());
			sorted.add(v);
		}
		
		Validator[] val = new Validator[sorted.size()];
		sorted.toArray(val);
		return val;
	}
	
	public static Validator[] getDefaultValidators(IProject project) throws InvocationTargetException {
		Map<String,Validator> extVals = ExtensionValidators.instance().getMap(project);
		Validator[] val = new Validator[extVals.size()];
		extVals.values().toArray(val);
		return val;
	}

	/**
	 * Answer all the registered validators.
	 * 
	 * @param project the project to use for getting the version 1 validator settings. This can
	 * be null in which case the global preferences are used.
	 * 
	 * @return Answer an empty array if there are no validators.
	 */
//	Validator[] getValidators2(IProject project) throws ProjectUnavailableError {
//		// If I use a local variable I don't need to synchronize the method.
//		
//		Validator[] validators = _validators;
//		if (project == null && validators != null)return validators;
//				
//		Validator[] val = loadExtensions(false, project);
//		ValPrefManagerGlobal vpm = ValPrefManagerGlobal.getDefault();
//		if (!vpm.loadPreferences(val)){
//			val = restoreDefaults2(project);
//			saveStateTimestamp();				
//		}
//		else {
//			if (getGlobalPreferences().getStateTimeStamp() != Platform.getStateStamp())
//				val = migrateSettings(val, project);
//		}
//		
//		TreeSet<Validator> set = new TreeSet<Validator>();
//		for (Validator v : val)set.add(v);
//		
//		List<Validator> list = new LinkedList<Validator>();
//		try {
//			ValidationConfiguration vc = ConfigurationManager.getManager().getConfiguration(project);
//			for (ValidatorMetaData vmd : vc.getValidators()){
//				list.add(Validator.create(vmd, vc, project));
//			}							
//			
//		}
//		catch (InvocationTargetException e){
//			if (project != null && (!project.exists() || !project.isOpen()))
//				throw new ProjectUnavailableError(project);
//			ValidationPlugin.getPlugin().handleException(e);
//		}
//		
//		set.addAll(list);
//		val = new Validator[set.size()];
//		set.toArray(val);
//		if (project == null)_validators = val;
//		return val;
//	}
	
	/**
	 * This method needs to be called whenever the validation configuration has changed.
	 */
	private void configHasChanged(){
		_configNumber.incrementAndGet();
		ValidatorProjectManager.reset();
		_cache.reset();
	}
		
	/**
	 * Answer the global validation preferences.
	 */
	public GlobalPreferences getGlobalPreferences(){
		GlobalPreferences gp = _globalPreferences.get();
		if (gp == null){
			ValPrefManagerGlobal vpm = ValPrefManagerGlobal.getDefault();
			gp = vpm.loadGlobalPreferences();
			if (!_globalPreferences.compareAndSet(null, gp))gp = _globalPreferences.get();
		}
		return gp;
	}
	
	/**
	 * Update the global preferences, but only if something has actually changed.
	 * @param values The global settings.
	 * @return a bit mask of the changes between the old values and the new values. See the GlobalPreferences
	 * constants for the bit mask values. If a zero is return there were no changes.
	 */
	public int replace(GlobalPreferencesValues values){
		GlobalPreferences gp = new GlobalPreferences(values);
		GlobalPreferences old = getGlobalPreferences();
		int changes = old.compare(gp);
		if (changes != 0){
			_globalPreferences.set(gp);
		}
		return changes;
	}
		
	/**
	 * Answer the project preferences for this project.
	 * @param project The project, this may be null.
	 */
	public ProjectPreferences getProjectPreferences(IProject project) {
		ProjectPreferences pp = _projectPreferences.get(project);
		if (pp != null)return pp;
		
		/* hopefully we rarely get this far */
		
		Map<String,Validator> extVals = ExtensionValidators.instance().getMapV2Copy();
		try {
			List<Validator> vals = ValPrefManagerGlobal.getDefault().getValidators();
			for (Validator v : vals)extVals.put(v.getId(), v);
			
			pp = getProjectPreferences(project, extVals);
		}
		catch (BackingStoreException e){
			ValidationPlugin.getPlugin().handleException(e);
		}	
		return pp;
	}

	/**
	 * 
	 * @param project The project, this may be null.
	 * @param baseValidators
	 */
	private ProjectPreferences getProjectPreferences(IProject project, Map<String, Validator> baseValidators) 
		throws BackingStoreException {
		ProjectPreferences pp = _projectPreferences.get(project);
		if (pp != null)return pp;
		
		ValPrefManagerProject vpm = new ValPrefManagerProject(project);
		pp = vpm.loadProjectPreferences(project, baseValidators);
		_projectPreferences.put(project, pp);
		return pp;		
	}
		
	/**
	 * Restore all the validation defaults, as defined by the individual validators via the
	 * validation extension point.
	 */
//	public synchronized void restoreDefaults() {
//		getGlobalPreferences().resetToDefault();
//		_validators = null;
//		getValidators(true);
//	}
	

	/**
	 * Run all the validators that are applicable to this resource.
	 * <p>
	 * If this is a manual validation both the version 1 and version 2 validators are run. If it
	 * is a build validation, then only the version 2 validators are run, because the old framework handles
	 * the running of the old validators.
	 * </p>
	 * 
	 * @param project project that is being validated
	 * 
	 * @param resource the resource that is being validated
	 * 
	 * @param kind the kind of resource delta. It will be one of the IResourceDelta constants, like
	 * IResourceDelta.CHANGED for example.
	 * 
	 * @param valType The type of validation request.
	 * @param buildKind the kind of build that triggered this validation. See IncrementalProjectBuilder for values.
	 * @param operation the operation that this validation is running under
	 * @param monitor the monitor to use to report progress 
	 */
	public void validate(IProject project, final IResource resource, final int kind, ValType valType, 
		int buildKind, ValOperation operation, final IProgressMonitor monitor) {
		
		MarkerManager.getDefault().deleteMarkers(resource, operation.getStarted(), IResource.DEPTH_ZERO);
		
		IValidatorVisitor visitor = new IValidatorVisitor(){

			public void visit(Validator validator, IProject project, ValType vt,
				ValOperation operation, IProgressMonitor monitor) {
								
				Validator.V1 v1 = validator.asV1Validator();
				if (vt == ValType.Build && v1 != null)return;
				
				SubMonitor subMonitor = SubMonitor.convert(monitor);
				String task = NLS.bind(ValMessages.LogValStart, validator.getName(), resource.getName());
				subMonitor.beginTask(task, 1);

				if (project.isOpen())
					validate(validator, operation, resource, kind, subMonitor.newChild(1), null);
			}			
		};
		SubMonitor sm = SubMonitor.convert(monitor, getValidators(project).length);
		accept(visitor, project, resource, valType, operation, sm);
		
	}
	
	/**
	 * Validate a single resource with a single validator. This will call the validator whether the validator
	 * is enabled or not.
	 * <p>
	 * Callers of this method should ensure that the shouldValidate was tested before making this call.
	 * 
	 * @param validator the validator
	 * @param operation the operation that the validation is running in.
	 * @param resource the resource to validate
	 * @param kind the kind of resource change. See IResourceDelta.
	 * @param monitor
	 */
	public void validate(Validator validator, ValOperation operation, IResource resource, int kind, 
			IProgressMonitor monitor, ValidationEvent event){
		if (operation.isValidated(validator.getId(), resource))return;
		long time = 0;
		long cpuTime = -1;
		String msg1 = NLS.bind(ValMessages.LogValStart, validator.getName(), resource.getName());
		monitor.subTask(msg1);
		IPerformanceMonitor pm = ValidationFramework.getDefault().getPerformanceMonitor();
		if (pm.isCollecting()){
			time = System.currentTimeMillis();
			cpuTime = Misc.getCPUTime();
		}
		
		if (Tracing.matchesExtraDetail(validator.getId())){
			Tracing.log("ValManager-03: validating ", resource); //$NON-NLS-1$
		}
	
		if (resource.exists())
		{	
			ValidationResult vr = validator.validate(resource, kind, operation, monitor, event);
	
			if (pm.isCollecting()){
				if (cpuTime != -1){
					cpuTime = Misc.getCPUTime() - cpuTime;
				}
				int num = 0;
				if (vr != null)num = vr.getNumberOfValidatedResources();
				PerformanceCounters pc = new PerformanceCounters(validator.getId(), 
					validator.getName(), resource.getName(),
					num, System.currentTimeMillis()-time, cpuTime);
				pm.add(pc);
			}
			if (ValidationPlugin.getPlugin().isDebugging() && !pm.isCollecting()){
				String msg = time != 0 ? 
					NLS.bind(ValMessages.LogValEndTime,	new Object[]{validator.getName(), 
						validator.getId(), resource, Misc.getTimeMS(System.currentTimeMillis()-time)}) :
					NLS.bind(ValMessages.LogValEnd, validator.getName(), resource);
				Tracing.log("ValManager-01: " + msg); //$NON-NLS-1$
			}
			if (vr != null){
				operation.mergeResults(vr);
				if (vr.getSuspendValidation() != null)operation.suspendValidation(vr.getSuspendValidation(), validator);
			}
		}
	}
	
	/**
	 * Accept a visitor for all the validators that are enabled for the given project.
	 * 
	 * @param visitor
	 * @param project
	 * @param valType the type of validation
	 * @param operation
	 * @param monitor
	 */
	public void accept(IValidatorVisitor visitor, IProject project, ValType valType, 
		ValOperation operation, IProgressMonitor monitor){
		
		if (isDisabled(project))return;
		
		for (Validator val : getValidators(project)){
			if (monitor.isCanceled())return;
			if (!ValidatorProjectManager.get().shouldValidate(val, project, valType))continue;
			if (operation.isSuspended(val, project))continue;
			try {
				visitor.visit(val, project, valType, operation, monitor);
			}
			catch (Exception e){
				ValidationPlugin.getPlugin().handleException(e);
			}
		}		
	}
	
	/**
	 * Accept a visitor for all the validators that are enabled for the given project, resource, 
	 * and validation mode.
	 * 
	 * @param valType the type of validation request
	 */
	public void accept(IValidatorVisitor visitor, IProject project, IResource resource, 
			ValType valType, ValOperation operation, IProgressMonitor monitor){
		
		if (isDisabled(project))return;
		
		Map<String,IValidatorGroupListener[]> groupListeners = new HashMap<String,IValidatorGroupListener[]>();
		
		ValProperty vp = getValProperty(resource, valType, _configNumber.get());
		if (vp != null){
			BitSet bs = vp.getConfigSet();
			for (Validator val : getValidators(project)){
				if (!monitor.isCanceled()) {
					if (!bs.get(_idManager.getIndex(val.getId())))continue;
					if (operation.isSuspended(val, project))continue;
					Validator.V2 v2 = val.asV2Validator();
					if (v2 != null) {
						notifyGroupListenersStarting(resource, operation.getState(), monitor, groupListeners, v2);
					}
					try {
						visitor.visit(val, project, valType, operation, monitor);
					}
					catch (Exception e){
						ValidationPlugin.getPlugin().handleException(e);
					}
				}
			}
			notifyGroupFinishing(resource, operation.getState(), monitor, groupListeners);
			return;
		}
		
		vp = new ValProperty();
		vp.setConfigNumber(_configNumber.get());
		ContentTypeWrapper ctw = new ContentTypeWrapper();
		for (Validator val : getValidators(project)){
			if (!monitor.isCanceled()) {
				if (!ValidatorProjectManager.get().shouldValidate(val, project, valType))continue;
				if (Friend.shouldValidate(val, resource, valType, ctw)){
					vp.getConfigSet().set(_idManager.getIndex(val.getId()));
					// we do the suspend check after figuring out if it needs to be validated, because we save
					// this information for the session.
					if (operation.isSuspended(val, project))continue;
					Validator.V2 v2 = val.asV2Validator();
					if (v2 != null) {
						notifyGroupListenersStarting(resource, operation.getState(), monitor, groupListeners, v2);
					}
					try {
						visitor.visit(val, project, valType, operation, monitor);
					}
					catch (Exception e){
						ValidationPlugin.getPlugin().handleException(e);
					}
				}
			}
		}
		notifyGroupFinishing(resource, operation.getState(), monitor, groupListeners);
		putValProperty(vp, resource, valType);
	}

	/**
	 * Let the group listeners know that validation might be starting for the group of validators. 
	 */
	private void notifyGroupListenersStarting(final IResource resource,	 
			final ValidationState state, final IProgressMonitor monitor, 
			Map<String, IValidatorGroupListener[]> groupListeners, Validator.V2 v2) {
		
		String[] groups = v2.getValidatorGroups();
		for (String group : groups) {
			if (!groupListeners.containsKey(group)) {
				IValidatorGroupListener[] createdListeners = null;
				try {
					createdListeners = ValidatorGroupExtensionReader.getDefault().createListeners(group);
				}
				catch (CoreException e){
					String msg = NLS.bind(ValMessages.ErrConfig, v2.getId());
					Status status = new Status(IStatus.ERROR, ValidationPlugin.PLUGIN_ID, msg);
					CoreException core = new CoreException(status);
					ValidationPlugin.getPlugin().handleException(core);
					ValidationPlugin.getPlugin().handleException(e);
					
					// we create this to ensure that we don't signal the same exception over and over. 
					createdListeners = new IValidatorGroupListener[0];
				}
				
				// create and notify just this once
				final IValidatorGroupListener[] listeners = createdListeners;
					
				groupListeners.put(group, listeners);
				for (final IValidatorGroupListener listener : listeners) {
					SafeRunner.run(new ISafeRunnable() {
						public void run() throws Exception {
							listener.validationStarting(resource, monitor, state);
						}

						public void handleException(Throwable exception) {
							ValidationPlugin.getPlugin().handleException(exception);
						}
					});
				}
			}
		}
	}

	/**
	 * Let the group listeners know that validation is finished for the group of validators. 
	 */
	private void notifyGroupFinishing(final IResource resource, 
			final ValidationState state, final IProgressMonitor monitor,
			Map<String, IValidatorGroupListener[]> groupListeners) {
		for (final IValidatorGroupListener[] listeners : groupListeners.values()) {
			for (final IValidatorGroupListener listener : listeners) {
				SafeRunner.run(new ISafeRunnable() {
					public void run() throws Exception {
						listener.validationFinishing(resource, monitor, state);
					}

					public void handleException(Throwable exception) {
						ValidationPlugin.getPlugin().handleException(exception);
					}
				});
			}
		}
	}

	private ValProperty getValProperty(IResource resource, ValType valType, int configNumber) {
		ValProperty vp = null;
		try {
			if (valType == ValType.Build)vp = (ValProperty)resource.getSessionProperty(StatusBuild);
			else if (valType == ValType.Manual)vp = (ValProperty)resource.getSessionProperty(StatusManual);
		}
		catch (CoreException e){
			// don't care about this one
		}
		if (vp == null)return null;
		if (vp.getConfigNumber() != _configNumber.get())return null;
		return vp;
	}
	
	/**
	 * Let the validation manager know that a project has been changed.
	 * 
	 * @param project The project that has been opened, created, or had it's description change.
	 */
	public void projectChanged(IProject project){
		ValidatorProjectManager.reset();
		_projectPreferences.remove(project);
		_cache.reset(project);
	}
	
	/**
	 * Let the validation manager know that a project has been removed.
	 * 
	 * @param project The project that has been closed or deleted.
	 * 
	 */
	public void projectRemoved(IProject project){
		ValidatorProjectManager.reset();
		_projectPreferences.remove(project);
		_cache.reset(project);
	}
	
	/**
	 * Clear the validation properties.
	 * @param resource
	 */
	public void clearValProperty(IResource resource){
		if (!resource.isAccessible())return;
		try {
			resource.setSessionProperty(StatusBuild, null);
			resource.setSessionProperty(StatusManual, null);
		} 
		catch (CoreException e) {
		}		
	}
	
	private void putValProperty(ValProperty vp, IResource resource, ValType valType) {
		try {
			if (!(resource.isAccessible())) return;
			if (valType == ValType.Build)resource.setSessionProperty(StatusBuild, vp);
			else if (valType == ValType.Manual)resource.setSessionProperty(StatusManual, vp);
		} 
		catch (CoreException e) {
	        // If the resource is not found, it is likely just been deleted 
	        // and there is no need to do anything. 
	        // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=269022
	        if (!e.getStatus().equals(IResourceStatus.RESOURCE_NOT_FOUND)) {
	                ValidationPlugin.getPlugin().handleException(e, IStatus.WARNING);
	        }
		}
	}

	/**
	 * Let each of the enabled validators know that a clean has been requested.
	 * 
	 * @param project the project that is being cleaned, or null if the entire workspace is being cleaned.
	 * @param monitor
	 */
	void clean(final IProject project, final ValOperation operation, final IProgressMonitor monitor) {
		IValidatorVisitor visitor = new IValidatorVisitor(){

			public void visit(Validator validator, IProject project, ValType valType,
				ValOperation operation, IProgressMonitor monitor) {
				validator.clean(project, operation, monitor);					
			}
			
		};
		accept(visitor, project, ValType.Build, operation, monitor);
	}
	
	/**
	 * Let each of the enabled validators know that a clean has been requested.
	 * 
	 * @param project the project that is being cleaned, or null if the entire workspace is being cleaned.
	 * @param monitor
	 */
	public void clean(IProject project, IProgressMonitor monitor){
		IValidatorVisitor visitor = new IValidatorVisitor(){

			public void visit(Validator validator, IProject project, ValType valType,
				ValOperation operation, IProgressMonitor monitor) {
				validator.clean(project, operation, monitor);					
			}
			
		};
		ValidationFramework.getDefault().getDependencyIndex().clear(project);
		ValOperation operation = new ValOperation();
		accept(visitor, project, ValType.Build, operation, monitor);
	}

	public void validatorsForProjectChanged(IProject project, boolean validationSettingChanged) {
		if (validationSettingChanged){
			if (project != null)_projectPreferences.remove(project);
			configHasChanged();
		}
	}
		
	private final class HasValidatorVisitor implements IResourceVisitor {
		
		private boolean 			_hasValidator;
		private final boolean		_isManual;
		private final boolean		_isBuild;
		
		public HasValidatorVisitor(boolean isManual, boolean isBuild){
			_isManual = isManual;
			_isBuild = isBuild;			
		}
		
		public boolean hasValidator(IFolder folder){
			try {
				folder.accept(this);
			}
			catch (CoreException e){
				ValidationPlugin.getPlugin().handleException(e);
			}
			return _hasValidator;
		}

		public boolean visit(IResource resource) throws CoreException {
			if (resource instanceof IFolder)return true;
			if (hasValidators(resource, _isManual, _isBuild)){
				_hasValidator = true;
				return false;
			}
			return true;
		}
	}
	
	/**
	 * Map validator id's to an index number on a bit set, so that we can quickly determine if a
	 * particular validator needs to validate a particular resource.
	 * @author karasiuk
	 *
	 */
	private final static class ValidatorIdManager {
		
		/**
		 * Map validator id's to Integers. The integers correspond to bits in the ValProperty instances.
		 */
		private final Map<String, Integer> _map = new HashMap<String, Integer>(100);
		
		/** Next available bit. */
		private int _next;
		
		/**
		 * Answer the index number for this validator. If we haven't seen it yet allocate a new index number.
		 * @param id validator id.
		 * @return index into the validator bit mask.
		 */
		public synchronized int getIndex(String id){
			Integer i = _map.get(id);
			if (i != null)return i;
			
			i = _next++;
			_map.put(id, i);
			
			return i;
		}				
	}
	
	/**
	 * This is used to keep track of which validators are enabled with which projects. We want to ensure
	 * that we don't activate a validator (and it's plug-in) if it has nothing to validate in the workspace.
	 * This is an immutable object.
	 * @author karasiuk
	 *
	 */
	private final static class ValidatorProjectManager {
		
		private final static AtomicReference<ValidatorProjectManager> _me = new AtomicReference<ValidatorProjectManager>();
		private final static AtomicInteger _counter = new AtomicInteger();
		
		private final ValProjectMap _manual = new ValProjectMap(ValType.Manual);
		private final ValProjectMap _build = new ValProjectMap(ValType.Build);
		private final int _sequence;
		
		/**
		 * Answer the most current ValidatorProjectManager creating a new one if you have to.
		 * @return
		 */
		public static ValidatorProjectManager get(){
			ValidatorProjectManager vpm = _me.get();
			if (vpm != null)return vpm;
			
			int next = _counter.incrementAndGet();
			ValidatorProjectManager newVpm = null;
			boolean looking = true;
			while(looking){
				vpm = _me.get();
				if (vpm == null || next > vpm.getSequence()){
					if (newVpm == null)newVpm = new ValidatorProjectManager(next);
					if (_me.compareAndSet(vpm, newVpm))return newVpm;
				}
				else looking = false;
			}
			return vpm;
		}
		
		/**
		 * Reset the ValidatorProjectManager to null, which will force a newer one to be created the next time
		 * that it is requested.
		 */
		public static void reset(){
			int next = _counter.incrementAndGet();
			ValidatorProjectManager vpm = _me.get();
			if ( vpm == null)return;
			if (next > vpm.getSequence())_me.compareAndSet(vpm, null);
		}
		
		private ValidatorProjectManager(int sequence){
			_sequence = sequence;
		}
		
		int getSequence(){
			return _sequence;
		}
		
		/**
		 * Should this validator attempt to validate any resources in this project?
		 * 
		 * @param validator
		 *            The validator that is being tested.
		 * @param project
		 *            The project that is being tested. This can be null, which
		 *            means that all projects will be tested.
		 * @param type
		 *            The type of validation operation.
		 * @return true if the validator should attempt to validate.
		 */
		public boolean shouldValidate(Validator validator, IProject project, ValType type){
			if (type == ValType.Build)return _build.shouldValidate(validator, project);
			if (type == ValType.Manual)return _manual.shouldValidate(validator, project);
				
			return false;
		}		
				
		/**
		 * This is used to keep track of which validators are enabled for which projects. We want to ensure
		 * that we don't activate a validator (and it's plug-in) if it has nothing to validate in the workspace.
		 * <p>
		 * There are two reasons why a validator may not be enabled. It's current project level filters may not match
		 * the project. Or the entire validator may have been turned off for the project. 
		 * </p>
		 * @author karasiuk
		 *
		 */
		private final static class ValProjectMap {
			/**
			 * Map a validator to the projects that it validates. This is an immutable object.
			 * <p>
			 * I've gone back and forth on whether the key should
			 * be a Validator or the validator id. I'm back to it being the id because I was
			 * running into cases where because of copying I wasn't getting the matches that I expected and I
			 * want to ensure that I don't leak validators. If I run into
			 * false matches, it is probably because reset isn't being called when it should be.
			 * </p>
			 */
			private final Map<String, Set<IProject>> _map;
			
			private final ValType _type;
						
			public ValProjectMap(ValType type){
				_type = type;
				_map = load();
			}
			
			/**
			 * Should this validator attempt to validate any resources in this project?
			 * 
			 * @param validator
			 *            The validator that is being tested.
			 * @param project
			 *            The project that is being tested. This can be null, which
			 *            means that all projects will be tested, and if any of them return true, 
			 *            then true is answered for this method.
			 *            
			 * @return true if the validator should attempt to validate.
			 */
			public boolean shouldValidate(Validator validator, IProject project){
				String vid = validator.getId();
				Set<IProject> projects = _map.get(vid);
				if (projects == null)return false;
				if (project == null)return projects.size() > 0;
				return projects.contains(project);
			}
			
			/**
			 * For each of the projects in the workspace, load which validators are currently prepared to validate things.
			 */
			private Map<String, Set<IProject>> load() {
				Map<String, Set<IProject>> map = new HashMap<String, Set<IProject>>(50);
				ValManager vm = ValManager.getDefault();
				IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
				Tracing.log("ValManager-02: loading " + projects.length + " projects");  //$NON-NLS-1$//$NON-NLS-2$
				for (IProject project : projects){
					if (!project.isOpen())continue;
					Validator[] vals = vm.getValidators(project);
					for (Validator v : vals){
						String vid = v.getId();
						Set<IProject> set = map.get(vid);
						if (set == null){
							set = new HashSet<IProject>(50);
							map.put(vid, set);
						}
						
						if (v.shouldValidateProject(project, _type))set.add(project);
					}					
				}
				return map;
			}
			
		}
		
	}

	public void handleEvent(IFacetedProjectEvent event) {
		_configNumber.incrementAndGet();
		projectChanged(event.getProject().getProject());
	}

	public void projectChanged(IProject project, int type) {
		switch (type){
		case IProjectChangeListener.ProjectClosed:
		case IProjectChangeListener.ProjectDeleted:
			projectRemoved(project);
			break;
		case IProjectChangeListener.ProjectOpened:
		case IProjectChangeListener.ProjectChanged:
		case IProjectChangeListener.ProjectAdded:
			projectChanged(project);
			break;
		}		
	}
	
	/**
	 * Store the singleton for the ValManager. This approach is used to avoid having to synchronize the
	 * ValManager.getDefault() method.
	 * 
	 * @author karasiuk
	 *
	 */
	private static class Singleton {
		static ValManager valManager = new ValManager();
	}
	
	private final class ValidatorCache {
		private final ConcurrentMap<IProject, Validator[]> _cache = new ConcurrentHashMap<IProject, Validator[]>(50);
		private final AtomicReference<Validator[]> _global = new AtomicReference<Validator[]>();
		
		public Validator[] getValidatorsCached(IProject project) throws ProjectUnavailableError {
			Validator[] vals = null;
			if (project == null){
				vals = _global.get();
				if (vals == null){				
					vals = getValidatorsNotCached(project);
					_global.set(vals);
				}
			}
			else {
				vals = _cache.get(project);
				if (vals == null){
					vals = getValidatorsNotCached(project);
					_cache.put(project, vals);
				}
			}
			return vals;
		}
		
		public void reset(){
			_cache.clear();
			_global.set(null);
		}
		
		public void reset(IProject project){
			if (project != null)_cache.remove(project);
		}

	}
	
	public enum UseProjectPreferences {Normal, MustUse, MustNotUse}

}
