/*******************************************************************************
 * Copyright (c) 2001, 2009 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.operations;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.wst.common.frameworks.internal.operations.IHeadlessRunnableWithProgress;
import org.eclipse.wst.validation.ValidationFramework;
import org.eclipse.wst.validation.internal.FilterUtil;
import org.eclipse.wst.validation.internal.IProjectValidationHelper;
import org.eclipse.wst.validation.internal.InternalValidatorManager;
import org.eclipse.wst.validation.internal.ReferencialFileValidatorRegistryReader;
import org.eclipse.wst.validation.internal.RegistryConstants;
import org.eclipse.wst.validation.internal.ResourceConstants;
import org.eclipse.wst.validation.internal.ResourceHandler;
import org.eclipse.wst.validation.internal.TaskListUtility;
import org.eclipse.wst.validation.internal.Tracing;
import org.eclipse.wst.validation.internal.ValManager;
import org.eclipse.wst.validation.internal.ValidationRegistryReader;
import org.eclipse.wst.validation.internal.ValidatorMetaData;
import org.eclipse.wst.validation.internal.core.IFileDelta;
import org.eclipse.wst.validation.internal.core.ValidationException;
import org.eclipse.wst.validation.internal.core.ValidatorLauncher;
import org.eclipse.wst.validation.internal.plugin.ValidationHelperRegistryReader;
import org.eclipse.wst.validation.internal.plugin.ValidationPlugin;
import org.eclipse.wst.validation.internal.provisional.core.IReporter;
import org.eclipse.wst.validation.internal.provisional.core.IValidationContext;
import org.eclipse.wst.validation.internal.provisional.core.IValidator;
import org.eclipse.wst.validation.internal.provisional.core.IValidatorJob;

/**
 * Validators must not be called directly by anyone other than this class, since
 * some initialization of the validator is done here (via the getProject() method). The
 * initialization is separated because the IProject isn't known until runtime.
 * <p>
 * This operation is not intended to be subclassed outside of the validation framework.
 * </p>
 */
public abstract class ValidationOperation implements IWorkspaceRunnable, IHeadlessRunnableWithProgress {
	// Since IResourceConstants don't have a "no delta" flag, let this constant be the flag.
	public static final int NO_DELTA_CHANGE = -1; 
	private static final String DELTA_AS_STRING = "IFileDelta[{0}] '{'{1}'}'"; //$NON-NLS-1$
	private static final String COMMA = ", "; //$NON-NLS-1$
	
	// For the depreciated constructors, by default the operation will not fork. 
	protected static final boolean DEFAULT_ASYNC = true;
	
	protected static final boolean DEFAULT_FORCE = true;
	
	private static ValidationLauncherJob launcherJob = new ValidationLauncherJob();
	
	private static final int jobsPerProcessor = 3;
	
	private final static class ValidationLauncherJob extends Job {
	    private Queue<Job> _validationJobs = new LinkedList<Job>();
	    
	    public ValidationLauncherJob() {
            super(ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_VALIDATION_JOB_MSG));
            setSystem(true);
            setRule(ResourcesPlugin.getWorkspace().getRoot());
	    }
	    
	    protected IStatus run(IProgressMonitor monitor) {
			int processors = Runtime.getRuntime().availableProcessors();
			int totalInitialJobs = processors * jobsPerProcessor;
    		synchronized (_validationJobs) {
    			// never schedule more than 3 validation jobs per processor at a time
    			for (int i=0; i< totalInitialJobs; i++) {
    				Job validationJob = _validationJobs.poll();
    				if (validationJob == null) break;
    				addJobChangeAdapter(validationJob);
    				validationJob.schedule();
    			}
    		}
            return Status.OK_STATUS;
	    }
	    
	    private void addJobChangeAdapter(Job job) {
	    	job.addJobChangeListener(new JobChangeAdapter(){
	    		// when done, see if there is another validation job to schedule
				public void done(IJobChangeEvent event) {
					synchronized (_validationJobs) {
						Job validationJob = _validationJobs.poll();
						if (validationJob != null) {
							addJobChangeAdapter(validationJob);
							validationJob.schedule();
						}
					}
				}
			});
	    }
	    
	    @Override
	    public boolean belongsTo(Object family) {
			if (family == ResourcesPlugin.FAMILY_MANUAL_BUILD)return true;
			if (family == ValidationBuilder.FAMILY_VALIDATION_JOB){
				return true;
			}
			return super.belongsTo(family);
	    }
	    
	    public void addValidationJob(Job validationJob) {
	    	synchronized (_validationJobs) {
	    		_validationJobs.add(validationJob);
	    		// schedule the job if we were empty
	    		if (_validationJobs.size() == 1) {
	    			this.schedule();
	    		}
	    	}
	    }
	}
	
	/**
	 * @deprecated Will be removed in Milestone 3. Use DEFAULT_ASYNC
	 */
	protected static final boolean DEFAULT_FORK = false; // @deprecated
	private IProject 		_project; // project to be validated
	private int 			_ruleGroup = RegistryConstants.ATT_RULE_GROUP_DEFAULT;
	private boolean _fork = DEFAULT_ASYNC; // do not fork the validation into a
	// different thread by default
	private Map<ValidatorMetaData, Set<IFileDelta>> _fileDeltas; // To reduce object creation,
	private IResourceDelta _delta;
	
	// the resource delta tree to be processed, or null if a full build/menu
	// option was triggered. This value is cached so that validation can be run
	// either from a builder, or from a menu item. (The Operation interface
	// doesn't allow any parameter on execute except the IProgressMonitor.)
	private Set<ValidatorMetaData> _enabledValidators;
	
	private boolean _force = DEFAULT_FORCE; // force this operation to run even if it doesn't need to?
	private boolean _isFullValidate; 	// Run a full validation or an incremental? (true = full)
	private Boolean _isAutoBuild; 		// Is the global auto-build preference enabled?
	private Set<ValidatorMetaData> _launchedValidators;
	
	protected IWorkbenchContext context;

	// are enabled and were launched (i.e., that have input to validate). For internal
	// validation framework use only;
	// it's needed for the automatic tests.
	/**
	 * This method is used for FINEST logging, to report exactly what deltas were about to be
	 * validated.
	 */
	private static final String getDeltaAsString(IFileDelta[] delta) {
		String args = ""; //$NON-NLS-1$
		int numArgs = 0;
		if (delta != null) {
			numArgs = delta.length;
			StringBuffer buffer = new StringBuffer();
			for (int i = 0; i < delta.length; i++) {
				buffer.append(COMMA);
				buffer.append(delta[i].toString());
			}
			buffer.replace(0, 1, ""); //$NON-NLS-1$ // magic numbers 0 and 1 => Remove first COMMA from the list (hence index 0); length of COMMA is 2, hence index 0, 1.
			args = buffer.toString();
		}
		return MessageFormat.format(DELTA_AS_STRING, new Object[]{String.valueOf(numArgs), args});
	}

	protected static void checkCanceled(WorkbenchReporter reporter) throws OperationCanceledException {
		if (reporter == null)return;
		else if (reporter.getProgressMonitor().isCanceled()) {
			throw new OperationCanceledException(""); //$NON-NLS-1$
		}
	}

	protected static boolean shouldForce(IResourceDelta delta) {
		return ((delta == null) ? DEFAULT_FORCE : false);
	}

	protected static boolean shouldForce(Object[] changedResources) {
		return (((changedResources == null) || (changedResources.length == 0)) ? DEFAULT_FORCE : false);
	}

	/**
	 * @deprecated. Will be removed in Milestone 3. Use ValidationOperation(project, boolean)
	 */
	public ValidationOperation(IProject project) {
		this(project, DEFAULT_ASYNC, DEFAULT_FORCE);
	}

	/**
	 * Internal.
	 */
	public ValidationOperation(IProject project, boolean force, boolean async) {
		this(project, null, null, RegistryConstants.ATT_RULE_GROUP_DEFAULT, force, async);
	}

	/**
	 * @deprecated. Will be removed in Milestone 3. Use ValidationOperation(project, int, boolean)
	 */
	public ValidationOperation(IProject project, int ruleGroup) {
		this(project, null, null, ruleGroup, DEFAULT_FORCE, DEFAULT_ASYNC);
	}

	/**
	 * @deprecated. Will be removed in Milestone 3. Use ValidationOperation(IProject,
	 *              IResourceDelta, Boolean, int, boolean, boolean)
	 */
	public ValidationOperation(IProject project, IResourceDelta delta, boolean isAutoBuild, int ruleGroup, boolean force, boolean fork) {
		this(project, delta, ((isAutoBuild) ? Boolean.TRUE : Boolean.FALSE), ruleGroup, fork, force);
	}

	/**
	 * Internal.
	 */
	protected ValidationOperation(IProject project, IResourceDelta delta, Boolean isAutoBuild, int ruleGroup, boolean force, boolean fork) {
		super();
		_project = project;
		_delta = delta;
		_isAutoBuild = isAutoBuild;
		_ruleGroup = ruleGroup;
		_fork = fork;
		_force = force;
		_enabledValidators = new HashSet<ValidatorMetaData>();
	}
	
	/**
	 * Internal.
	 */
	protected ValidationOperation(IProject project, IWorkbenchContext aContext, IResourceDelta delta, Boolean isAutoBuild, int ruleGroup, boolean force, boolean fork) {
		super();
		_project = project;
		_delta = delta;
		_isAutoBuild = isAutoBuild;
		_ruleGroup = ruleGroup;
		_fork = fork;
		_force = force;
		_enabledValidators = new HashSet<ValidatorMetaData>();
		context = aContext;
	}

	/**
	 * @deprecated Will be removed in Milestone 3.
	 */
	protected void terminateCleanup(WorkbenchReporter reporter) {
		for (ValidatorMetaData vmd : getEnabledValidators()) {
			reporter.displaySubtask(ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_VALIDATOR_CLEANUP, new String[]{vmd.getValidatorDisplayName()}));
			try {
				reporter.removeAllMessages(vmd.getValidator());
			} catch (InstantiationException e) {
				// Remove the vmd from the reader's list
				ValidationRegistryReader.getReader().disableValidator(vmd);
				ValidationPlugin.getPlugin().handleException(e);
				continue;
			}
			addCancelTask(vmd);
			reporter.displaySubtask(ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_VALIDATOR_TERMINATED, new String[]{getProject().getName(), vmd.getValidatorDisplayName()}));
		}
	} 
	

	/**
	 * @param vmd
	 * @param delta
	 * @param logger
	 * @param start
	 */
//	private void logValidationInfo(ValidatorMetaData vmd, IFileDelta[] delta, Logger logger, long start) {
//		long finish = System.currentTimeMillis();
//		TimeEntry entry = ValidationPlugin.getTimeEntry();
//		entry.setSourceID("ValidationOperation.launchValidator"); //$NON-NLS-1$
//		entry.setProjectName(getProject().getName());
//		entry.setToolName(vmd.getValidatorUniqueName());
//		entry.setElapsedTime(finish - start);
//		if (logger.isLoggingLevel(Level.FINE)) {
//			StringBuffer buffer = new StringBuffer();
//			if (isFullValidate()) {
//				buffer.append("EVERYTHING"); //$NON-NLS-1$
//			} else {
//				if (delta.length == 0) {
//					buffer.append("NOTHING"); //$NON-NLS-1$
//				} else {
//					buffer.append(getDeltaAsString(delta));
//				}
//			}
//			entry.setDetails(buffer.toString());
//		}
//		logger.write(Level.INFO, entry);
//	}

	/**
	 * @param reporter
	 * @param vmd
	 * @param logger
	 * @param exc
	 */
//	private void handleThrowables(WorkbenchReporter reporter, ValidatorMetaData vmd, Logger logger, Throwable exc) {
//		// If a runtime exception has occured, e.g. NullPointer or ClassCast,
//		// display it with the "A runtime exception has occurred " messsage.
//		// This will provide more information to the user when he/she calls IBM
//		// Service.
//		if (logger.isLoggingLevel(Level.SEVERE)) {
//			LogEntry entry = ValidationPlugin.getLogEntry();
//			entry.setSourceID("ValidationOperation::launchValidator"); //$NON-NLS-1$
//			entry.setTargetException(exc);
//			logger.write(Level.SEVERE, entry);
//		}
//		String[] msgParm = {exc.getClass().getName(), vmd.getValidatorDisplayName(), (exc.getMessage() == null ? "" : exc.getMessage())}; //$NON-NLS-1$
//		Message message = ValidationPlugin.getMessage();
//		message.setSeverity(IMessage.NORMAL_SEVERITY);
//		message.setId(ResourceConstants.VBF_EXC_RUNTIME);
//		message.setParams(msgParm);
//		try {
//			reporter.addMessage(vmd.getValidator(), message);
//		} catch (InstantiationException exc2) {
//			handleInstantiationException(vmd, logger, exc2);
//		} catch (MessageLimitException e) {
//			throw e;
//		}
//		return;
//	}

	/**
	 * @param vmd
	 * @param logger
	 * @param exc2
	 */
//	private void handleInstantiationException(ValidatorMetaData vmd, Logger logger, InstantiationException exc2) {
//		// Remove the vmd from the reader's list
//		ValidationRegistryReader.getReader().disableValidator(vmd);
//		// Log the reason for the disabled validator
//		if (logger.isLoggingLevel(Level.SEVERE)) {
//			LogEntry entry = ValidationPlugin.getLogEntry();
//			entry.setSourceID("ValidationOperation::launchValidator (deprecated)"); //$NON-NLS-1$
//			entry.setTargetException(exc2);
//			logger.write(Level.SEVERE, entry);
//		}
//	}

	/**
	 * @param reporter
	 * @param vmd
	 * @param logger
	 * @param exc
	 */
//	private void handleValidationExceptions(WorkbenchReporter reporter, ValidatorMetaData vmd, Logger logger, ValidationException exc) {
//		// First, see if a validator just caught all Throwables and
//		// accidentally wrapped a MessageLimitException instead of propagating
//		// it.
//		if (exc.getAssociatedException() != null) {
//			if (exc.getAssociatedException() instanceof MessageLimitException) {
//				MessageLimitException mssgExc = (MessageLimitException) exc.getAssociatedException();
//				throw mssgExc;
//			} else if (exc.getAssociatedException() instanceof ValidationException) {
//				try {
//					ValidationException vexc = (ValidationException) exc.getAssociatedException();
//					vexc.setClassLoader(vmd.getValidator().getClass().getClassLoader()); // first,
//					// set the class loader,so that the exception's getMessage() method can retrieve
//					// the resource bundle
//				} catch (InstantiationException exc2) {
//					handleInstantiationException(vmd, logger, exc2);
//				}
//			}
//		}
//		// If there is a problem with this particular validator, log the error
//		// and continue
//		// with the next validator.
//		try {
//			exc.setClassLoader(vmd.getValidator().getClass().getClassLoader()); // first,
//			// set the class loader,so that the exception's getMessage() method can retrieve the
//			// resource bundle
//		} catch (InstantiationException exc2) {
//			handleInstantiationException(vmd, logger, exc2);
//		}
//		if (logger.isLoggingLevel(Level.SEVERE)) {
//			LogEntry entry = ValidationPlugin.getLogEntry();
//			entry.setSourceID("ValidationOperation.validate(WorkbenchMonitor)"); //$NON-NLS-1$
//			entry.setTargetException(exc);
//			logger.write(Level.SEVERE, entry);
//			if (exc.getAssociatedException() != null) {
//				entry.setTargetException(exc.getAssociatedException());
//				logger.write(Level.SEVERE, entry);
//			}
//		}
//		String message = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_ENDING_VALIDATION_ABNORMALLY, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
//		reporter.displaySubtask(message);
//		if (exc.getAssociatedMessage() != null) {
//			try {
//				reporter.addMessage(vmd.getValidator(), exc.getAssociatedMessage());
//			} catch (InstantiationException exc2) {
//				handleInstantiationException(vmd, logger, exc2);
//			}
//		}
//	}

	/**
	 * @param reporter
	 * @param vmd
	 * @param logger
	 * @param exc
	 */
//	private void handleHelperCleanupExceptions(WorkbenchReporter reporter, ValidatorMetaData vmd, Logger logger, Throwable exc) {
//		// If a runtime exception has occured, e.g. NullPointer or ClassCast,
//		// display it with the "A runtime exception has occurred " messsage.
//		// This will provide more information to the user when he/she calls IBM
//		// Service.
//		if (logger.isLoggingLevel(Level.SEVERE)) {
//			LogEntry entry = ValidationPlugin.getLogEntry();
//			entry.setSourceID("ValidationOperation::launchValidator"); //$NON-NLS-1$
//			entry.setTargetException(exc);
//			logger.write(Level.SEVERE, entry);
//		}
//		String[] msgParm = {exc.getClass().getName(), vmd.getValidatorDisplayName(), (exc.getMessage() == null ? "" : exc.getMessage())}; //$NON-NLS-1$
//		Message message = ValidationPlugin.getMessage();
//		message.setSeverity(IMessage.NORMAL_SEVERITY);
//		message.setId(ResourceConstants.VBF_EXC_RUNTIME);
//		message.setParams(msgParm);
//		try {
//			reporter.addMessage(vmd.getValidator(), message);
//		} catch (InstantiationException exc2) {
//			handleInstantiationException(vmd, logger, exc2);
//		} catch (MessageLimitException e) {
//			throw e;
//		}
//		return;
//	}

	public boolean isFork() {
		return _fork;
	}

	public boolean isForce() {
		return _force;
	}

	public void setForce(boolean force) {
		_force = force;
	}

	/**
	 * If the code that invoked this operation suspended auto-build before invoking this operation,
	 * the user's auto-build setting is stored in the following methods.
	 */
	public boolean isAutoBuild() {
		if (_isAutoBuild == null) {
			return ValidatorManager.getManager().isGlobalAutoBuildEnabled();
		}
		return _isAutoBuild.booleanValue();
	}

	protected void setAutoBuild(boolean autoOn) {
		_isAutoBuild = ((autoOn) ? Boolean.TRUE : Boolean.FALSE);
	}

	protected boolean isFullValidate() {
		return _isFullValidate;
	}

	private void setFullValidate(boolean b) {
		_isFullValidate = b;
	}

	protected int getRuleGroup() {
		return _ruleGroup;
	}

	/**
	 * Return true if the given validator must run (i.e., it has changes to validate, and it was not
	 * run automatically.)
	 */
	private boolean isValidationNecessary(ValidatorMetaData vmd, IFileDelta[] delta) {
		// Validation is not necessary if there are no files for the validator to validate.
		return isFullValidate() || (delta.length > 0);
	}

	/**
	 * Return true if, given the enabled validators and file deltas, there is work for this
	 * operation to do.
	 */
	public boolean isNecessary(IProgressMonitor monitor) throws CoreException, OperationCanceledException {
		Set<ValidatorMetaData> enabledValidators = getEnabledValidators();
		if ((enabledValidators == null) || (enabledValidators.size() == 0)) {
			return false;
		}
		if (isFullValidate())return true;
		
		for (ValidatorMetaData vmd : enabledValidators) {
			if (isValidationNecessary(vmd, getFileDeltas(monitor, vmd)))return true;
		}
		return false;
	}

	private Map<ValidatorMetaData, Set<IFileDelta>> getFileDeltas(IProgressMonitor monitor) throws CoreException {
		if (_fileDeltas == null)loadFileDeltas(monitor);
		return _fileDeltas;
	}

	private IFileDelta[] getFileDeltas(IProgressMonitor monitor, ValidatorMetaData vmd) throws CoreException {
		Set<IFileDelta> result = getFileDeltas(monitor).get(vmd);
		if (result == null)return new IFileDelta[0];
		
		IFileDelta[] temp = new IFileDelta[result.size()];
		result.toArray(temp);
		return temp;
	}

	/**
	 * Store the file deltas (VMD <=>Set[IFileDelta]) if the file deltas haven't been loaded. If the
	 * deltas have already been loaded, return without doing anything.
	 */
	private void loadFileDeltas(IProgressMonitor monitor) throws CoreException {
		// Although, for a full build, we don't build up a list of changed
		// files, we do need to notify each IWorkbenchContext that an
		// IResource has been filtered in.
		// It's a full validation if the IResourceDelta is null and the
		// Object[] (or IResource[]) is also null.
		// i.e., it's a full validation if no incremental input has been set.
		setFullValidate((getDelta() == null) && (_fileDeltas == null));
		if (isFullValidate()) {
			_fileDeltas = FilterUtil.loadDeltas(monitor, getEnabledValidators(), getProject());
		} else {
			_fileDeltas = FilterUtil.loadDeltas(monitor, getEnabledValidators(), getDelta()); // traverse,
			// and process, each resource in the delta tree
		}
	}

	protected void setFileDeltas(Map<ValidatorMetaData, Set<IFileDelta>> deltas) {
		_fileDeltas = deltas;
	}

	protected IResourceDelta getDelta() {
		return _delta;
	}

	protected void setDelta(IResourceDelta delta) {
		_delta = delta;
	}

	protected boolean areValidatorsEnabled() {
		return (getEnabledValidators().size() != 0);
	}

	/**
	 * Return the validators which are both configured on this type of project, (as stored in
	 * getProject()), and enabled by the user on this project.
	 */
	public Set<ValidatorMetaData> getEnabledValidators() {
		return _enabledValidators;
	}

	/**
	 * This is an internal method, subject to change without notice. It is provided only for the
	 * automated validation framework tests.
	 */
	public Set<ValidatorMetaData> getLaunchedValidators() {
		if (_launchedValidators == null) {
			_launchedValidators = new HashSet<ValidatorMetaData>();
		}
		return _launchedValidators;
	}

	protected void setEnabledValidators(Set<ValidatorMetaData> evmds) {
		// Check that every VMD in the set is configured on this project.
		// Necessary because the user can manually choose which validators
		// to launch, and the validator may not be installed.
		_enabledValidators.clear();
		for (ValidatorMetaData vmd : evmds) {
			if (ValidationRegistryReader.getReader().isConfiguredOnProject(vmd, getProject())) {
				_enabledValidators.add(vmd);
			}
		}
	}

	/**
	 * This method returns the IProject that this ValidationOperation was created with.
	 */
	public IProject getProject() {
		return _project;
	}

	protected int getUnitsOfWork() {
		/*
		 * Let one unit of work equal one resource. number of enabled validators. i.e., each
		 * enabled validator must process (at most) each resource in the project; count each process
		 * as one unit of work. Note that this is a ceiling number, because if we're doing an
		 * incremental validation, not all resources will be validated.
		 * setNumResources(countResources(getProject())); getEnabledValidators().size();
		 */
		// Until the validators can report units-of-work complete,
		// initialize the monitor with an unknown amount of work.
		// (So the user will see movement in the progress bar, even
		// if the movement doesn't indicate the amount of work done.)
		return IProgressMonitor.UNKNOWN;
	}

	/**
	 * If the user is cancelling validation on the current project/resource, Add an information task
	 * to the task list informing the user that validation has not been run on the current project.
	 */
	protected void addCancelTask(ValidatorMetaData vmd) {
		InternalValidatorManager.getManager().addOperationTask(getProject(), vmd, ResourceConstants.VBF_STATUS_VALIDATOR_TERMINATED, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
	}

	/**
	 * This method starts the validation of each configured enabled validator on the current
	 * project.
	 * 
	 * The IProgressMonitor passed in must not be null.
	 */
	public void run(IProgressMonitor progressMonitor) throws OperationCanceledException {
		try {
			// In order to check whether or not the monitor has been canceled, the monitor must not be null.
			if (progressMonitor == null)return;
			IProject project = getProject();
			if (ValidationFramework.getDefault().isSuspended(project))return;
			if (ValManager.getDefault().isDisabled(project))return;
			
			if (!areValidatorsEnabled()) {
				// save some processing time...
				return;
			}
			
			final WorkbenchReporter reporter = new WorkbenchReporter(getProject(), progressMonitor);

			try {
				// Periodically check if the user has canceled the operation
				checkCanceled(reporter);
				preValidate(reporter);
				validate(reporter);
				validateReferencialFiles(reporter);
			} catch (CoreException e) {
				ValidationPlugin.getPlugin().handleException(e);
			}
		} finally {
			//TODO GRK determine if timing info should be added here
//			if (logger.isLoggingLevel(Level.FINE)) {
//				long finish = System.currentTimeMillis();
//				TimeEntry entry = ValidationPlugin.getTimeEntry();
//				entry.setSourceID("ValidationOperation.run(WorkbenchMonitor)"); //$NON-NLS-1$
//				entry.setProjectName(getProject().getName());
//				entry.setToolName("ValidationOperation"); //$NON-NLS-1$
//				entry.setElapsedTime(finish - start);
//				logger.write(Level.FINE, entry);
//			}
		}
	}

	private void validateReferencialFiles(WorkbenchReporter reporter) {
		ReferencialFileValidatorRegistryReader reader = ReferencialFileValidatorRegistryReader.getInstance();
		if (reader != null) {
			reader.readRegistry();
			ReferencialFileValidator refFileValidator = reader.getReferencialFileValidator();
			if (refFileValidator != null) {
				if (_delta != null) {
					refFileValidateFileDelta(reporter, refFileValidator);
				} else if (_project != null) {
					postValidateProject(reporter, refFileValidator);
				}
			}
		}
	}

	private void refFileValidateFileDelta(WorkbenchReporter reporter, ReferencialFileValidator refFileValidator) {
		IResourceDelta[] resourceDelta = _delta.getAffectedChildren(IResourceDelta.ADDED | IResourceDelta.CHANGED | IResourceDelta.REMOVED);
		List<IResource> inputFiles = new ArrayList<IResource>();
		
		List<IFile> referencingFiles = new ArrayList<IFile>();
		if (resourceDelta != null && resourceDelta.length > 0) {
			for (int i = 0; i < resourceDelta.length; i++) {
				IResource resource = resourceDelta[i].getResource();
				if (resource instanceof IFolder) {
					getFileResourceDeltaInFolder(resourceDelta[i], inputFiles);
				} else if (resource instanceof IFile)
					inputFiles.add(resource);
			}
			List<IFile> rFilesToValidate = refFileValidator.getReferencedFile(inputFiles);
			if (rFilesToValidate != null && !rFilesToValidate.isEmpty())
				referencingFiles.addAll(rFilesToValidate);
			try {
				if (!referencingFiles.isEmpty())
					validateReferencingFiles(reporter, referencingFiles);
			} catch (Exception e) {
				ValidationPlugin.getPlugin().handleException(e);
			}
		}
	}

	private void getFileResourceDeltaInFolder(IResourceDelta delta, List<IResource> inputFiles) {
		IResourceDelta[] resourceDelta = delta.getAffectedChildren();
		for (int i = 0; i < resourceDelta.length; i++) {
			IResource resource = resourceDelta[i].getResource();
			if (resource instanceof IFile)inputFiles.add(resource);
			else if (resource instanceof IFolder)getFileResourceDeltaInFolder(resourceDelta[i], inputFiles);
		}
	}

	private void postValidateProject(WorkbenchReporter reporter, ReferencialFileValidator refFileValidator) {
		Set<ValidatorMetaData> set = ValidationRegistryReader.getReader().getValidatorMetaData(_project);
		for (ValidatorMetaData data : set) {
			List<String> filters = data.getNameFilters();
			List<IFile> files = getAllFilesForFilter(filters);
			if (!files.isEmpty()) {
				List<IFile> fileForValidation = refFileValidator.getReferencedFile(files);
				try {
					validateReferencingFiles(reporter, fileForValidation);
				} catch (Exception e) {
					ValidationPlugin.getPlugin().handleException(e);
				}
			}
		}
	}

	private List<IFile> getAllFilesForFilter(List<String> filters) {
		if (!filters.isEmpty()) {
			List<IFile> allProjectFiles = ReferencialFileValidatorHelper.getAllProjectFiles(_project);
			List<IFile> filterFiles = new ArrayList<IFile>();
			for (String fileName : filters) {
				if (fileName == null)continue;
				
				for (IFile projectFile : allProjectFiles) {
					if (fileName.charAt(0) == '*') {
						String extName = fileName.substring(2, fileName.length());
						String ext = projectFile.getFileExtension();
						if (ext != null && ext.equals(extName))filterFiles.add(projectFile);
					} else if (fileName.equals(projectFile.getName()))filterFiles.add(projectFile);
				}
			}
			return filterFiles;
		}
		return new LinkedList<IFile>();
	}

	private void validateReferencingFiles(IReporter reporter, List<IFile> referencingFiles) throws Exception {
		Set<IFile> validatedFiles = new HashSet<IFile>();
		for (IFile refFile : referencingFiles) {
			if (!validatedFiles.contains(refFile)) {
				IResource resource = refFile.getParent();
				IProject project = null;
				if (resource != null && !(resource instanceof IProject))
					project = getProjectContainer(resource);
				else
					project = (IProject) resource;
				if (project != null) {
					Set<ValidatorMetaData> set = ValidationRegistryReader.getReader().getValidatorMetaData(project);
					for (ValidatorMetaData data : set) {
						if (data.isApplicableTo(refFile)) {
							IValidator validator = data.getValidator();
							validator.validate(data.getHelper(project),reporter);
							validatedFiles.add(refFile);
						}
					}
				}
			}
		}
	}

	private IProject getProjectContainer(IResource resource) {
		IResource pResource = resource.getParent();
		if (!(pResource instanceof IProject))
			return getProjectContainer(pResource);
		return (IProject) pResource;
	}

	protected void preValidate(WorkbenchReporter reporter) throws CoreException, OperationCanceledException {
		// Load the input.
		getFileDeltas(reporter.getProgressMonitor());
	}

	/**
	 * Iterate over all of the enabled validators and run the thread-safe validators in a background
	 * thread, and the not-thread-safe validators in this thread.
	 */
	protected void validate(WorkbenchReporter reporter) throws OperationCanceledException {
		if (reporter == null)return;
		checkCanceled(reporter);
		reporter.getProgressMonitor().beginTask(ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_PROGRESSMONITOR_TITLE), getUnitsOfWork());
		IValidator validator = null;
		Iterator<ValidatorMetaData> iterator = null;
		IFileDelta[] delta = null;
		
		Set<ValidatorMetaData> jobValidators = new HashSet<ValidatorMetaData>();
		Set<ValidatorMetaData> validators = new HashSet<ValidatorMetaData>();
				
		for(ValidatorMetaData vmd : getEnabledValidators()){
			IValidator valInstance = null;
			try {
				valInstance = vmd.getValidator();
			} 
			catch (InstantiationException e1) {
				if(!ValidatorManager.getManager().getProblemValidators().contains(vmd)) {
					ValidatorManager.getManager().getProblemValidators().add(vmd);
					System.out.println(e1.getMessage());
				}
			}
			
			if( isFork() && (valInstance != null) && valInstance instanceof IValidatorJob ){
				try {
					delta = getFileDeltas(reporter.getProgressMonitor(), vmd);
				} catch (CoreException e) {
					e.printStackTrace();
				}
				boolean willRun = (isForce() || isValidationNecessary(vmd, delta));
				if( willRun )jobValidators.add( vmd );
			}
			else if (valInstance != null){
				validators.add( vmd );
			}
		}
		if( jobValidators.size() > 0 ){
			launchJobs( jobValidators, reporter );
		}
				
		ValidatorMetaData currentVmd = null;
		try {
			for (ValidatorMetaData vmd : validators) {
				currentVmd = vmd;
				// Is validation about to be run on this validator?
				// Validation will run either if this operation forces regardless
				// of need, or if the validator was not run automatically.
				// If validation is not about to be run, then don't activate
				// the plug-in
				try {
					delta = getFileDeltas(reporter.getProgressMonitor(), vmd);
					boolean willRun = (isForce() || isValidationNecessary(vmd, delta));
					if (Tracing.isTraceV1()) {
						StringBuffer buffer = new StringBuffer();
						buffer.append("ValidationOperation-01: will run? "); //$NON-NLS-1$
						buffer.append(willRun);
						buffer.append("  "); //$NON-NLS-1$
						buffer.append("is force? "); //$NON-NLS-1$
						buffer.append(isForce());
						buffer.append("  "); //$NON-NLS-1$
						buffer.append("isAutoBuild? "); //$NON-NLS-1$
						buffer.append(_isAutoBuild);
						buffer.append("  "); //$NON-NLS-1$
						buffer.append("isAutoValidate? "); //$NON-NLS-1$
						buffer.append("  "); //$NON-NLS-1$
						buffer.append("isIncremental? "); //$NON-NLS-1$
						buffer.append(vmd.isIncremental());
						buffer.append("  "); //$NON-NLS-1$
						if (isFullValidate()) {
							buffer.append("EVERYTHING"); //$NON-NLS-1$
						} else {
							if (delta.length == 0) {
								buffer.append("NOTHING"); //$NON-NLS-1$
							} else {
								buffer.append(getDeltaAsString(delta));
							}
						}
						Tracing.log(buffer);
					}
					if (!willRun) {
						continue;
					}
				} catch (CoreException e) {
					ValidationPlugin.getPlugin().handleException(e);
					String mssg = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_ENDING_VALIDATION_ABNORMALLY, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
					reporter.displaySubtask(mssg);
					continue;
				}
				try {
					context = vmd.getHelper(getProject());
					initValidateContext(delta);
					validator = vmd.getValidator();
					
					checkCanceled(reporter);
					
				} catch (InstantiationException e) {
					// Remove the vmd from the reader's list
					ValidationRegistryReader.getReader().disableValidator(vmd);
					ValidationPlugin.getPlugin().handleException(e);
					continue;
				}
				
//				if (isFork() && vmd.isAsync()) {
//					// Don't appear to run in the foreground by sending
//					// progress to the IProgressMonitor in the
//					// WorkbenchMonitor. Suppress the status messages by
//					// changing the IProgressMonitor to a
//					// NullProgressMonitor.
//					VThreadManager.getManager().queue(wrapInRunnable(nullReporter, validator, vmd,(WorkbenchContext)getContext(),delta, iterator));
//				} else {
//					internalValidate(reporter, validator, vmd, context, delta);
//				}
				internalValidate(reporter, validator, vmd, context, delta);
			}
		} 
		catch (OperationCanceledException exc) {
			handleOperationCancelledValidateException(reporter, validator, currentVmd, iterator, exc);
		} 
		finally {
			releaseCachedMaps();
		}
	}

	private void releaseCachedMaps() {
		ValidationRegistryReader.getReader().clearCachedMaps();
		IProjectValidationHelper helper = ValidationHelperRegistryReader.getInstance().getValidationHelper();
		if (helper != null)
			helper.disposeInstance();
	}

	private void initValidateContext(IFileDelta[] delta) {
		 if (context instanceof WorkbenchContext) {
			 ((WorkbenchContext)context).setValidationFileURIs(new ArrayList<String>());
			 for(IFileDelta file : delta) {
				 if(file.getDeltaType() != IFileDelta.DELETED ) {
					 ((WorkbenchContext)context).getValidationFileURIs().add(file.getFileName());
				 }
			 } 
		}
	}

	/**
	 * @param reporter
	 * @param validator
	 * @param vmd
	 * @param iterator
	 * @param logger
	 * @param exc
	 */
	private void handleOperationCancelledValidateException(WorkbenchReporter reporter, IValidator validator, 
		ValidatorMetaData vmd, Iterator<ValidatorMetaData> iterator, OperationCanceledException exc) {
		/*
		 * If the user terminates validation (i.e., presses "cancel" on the progress monitor) before
		 * the validation completes, perform clean up on each configured enabled validator.
		 * 
		 * To clean up, several steps are performed: 1. call <code></code> on each configured
		 * enabled validator, so that each validator can perform cleanup that it knows is necessary.
		 * 2. remove all tasks that this validator has added to the task list 3. add another task to
		 * the task list to say that validation, using this validator on this project, was
		 * terminated.
		 * 
		 * Steps 2 and 3 are done so that it's clear what has, and has not, been validated. If these
		 * steps weren't performed, validation could be done on some items in the project, but not
		 * others; and the user could mistakenly believe that those are the only problems with the
		 * project. Unless the user knows that a full verification needs to be done, he/she could
		 * continue to rely on automatic verification, and never know that there are problems with a
		 * resource which hasn't been validated.
		 */
		reporter.displaySubtask(ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_VALIDATOR_CLEANUP, new String[]{vmd.getValidatorDisplayName()}));
		reporter.removeAllMessages(validator);
		addCancelTask(vmd);
		reporter.displaySubtask(ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_VALIDATOR_TERMINATED, new String[]{getProject().getName(), vmd.getValidatorDisplayName()}));
		while (iterator.hasNext()) {
			vmd = (ValidatorMetaData) iterator.next();
			try {
				validator = vmd.getValidator();
			} catch (InstantiationException exc2) {
				// Remove the vmd from the reader's list
				ValidationRegistryReader.getReader().disableValidator(vmd);
				ValidationPlugin.getPlugin().handleException(exc2);
				continue;
			}
			reporter.displaySubtask(ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_VALIDATOR_CLEANUP, new String[]{vmd.getValidatorDisplayName()}));
			reporter.removeAllMessages(validator);
			addCancelTask(vmd);
			reporter.displaySubtask(ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_VALIDATOR_TERMINATED, new String[]{getProject().getName(), vmd.getValidatorDisplayName()}));
		}
		throw exc; // propagate the exception up to the framework so that
		// the framework can display the correct "canceled"
		// message in the dialog

	}

	/* package */
	void internalValidate(final WorkbenchReporter reporter, final IValidator validator, final ValidatorMetaData vmd,final IWorkbenchContext aContext, final IFileDelta[] delta) throws OperationCanceledException {
		try {
			checkCanceled(reporter);
			removeOldMessages(reporter, validator, vmd, delta);
			// Do NOT check if the message limit is exceeded before launching
			// the validator.
			// Even if the limit is exceeded when the messages are removed from
			// the delta
			// files, it could be that the validator itself will remove
			// messages before
			// proceeding. Let the validator run so that it can remove messages
			// if it
			// needs to, and if it tries to add a message when the limit is
			// exceeded, let
			// the WorkbenchReporter take care of it.
			launchValidator(reporter, validator, vmd, aContext, delta);
		} catch (OperationCanceledException exc) {
			// This is handled in the validate(WorkbenchReporter) method.
			throw exc;
		}catch (Exception e) {
			// If there is a problem with this particular validator, log the
			// error and continue with the next validator.
			// If a runtime exception has occurred, e.g. NullPointer or
			// ClassCast, display it with the "A runtime exception has occurred" message.
			// This will provide more information to the user when he/she calls Service.
			ValidationPlugin.getPlugin().handleException(e);
			String mssg = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_ENDING_VALIDATION_ABNORMALLY, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
			reporter.displaySubtask(mssg);
			
			/*
			 The code  below  causes bundle not found exception since, the  bundle here is
			 validate_base and we  are  trying to load that bundle from the classloader of 
			 the Validator. 
			  
			String[] msgParm = {exc.getClass().getName(), vmd.getValidatorDisplayName(), (exc.getMessage() == null ? "" : exc.getMessage())}; //$NON-NLS-1$
			Message message = ValidationPlugin.getMessage();
			message.setSeverity(IMessage.NORMAL_SEVERITY);
			message.setId(ResourceConstants.VBF_EXC_RUNTIME);
			message.setParams(msgParm);
			reporter.addMessage(validator, message);
			*/
		} finally {
			// If user fixes problem, and limit exceeded, add "exceeded"
			// message, or
			// if limit not exceeded any more, remove "exceeded" message.
			//Message Limit is removed from the framework
			//ValidatorManager.getManager().checkMessageLimit(getProject(), true);
			reporter.getProgressMonitor().done();
		}
	}

	/**
	 * In order to allow validators to run, must first check if there's space for new markers. But
	 * the old markers must not prevent validation from running again (limit exceeded), so delete
	 * all of the old markers first, and then run validation.
	 */
	private final void removeOldMessages(WorkbenchReporter reporter, IValidator validator, ValidatorMetaData vmd, IFileDelta[] delta) {
		if (reporter == null) {
			return;
		}
		// If the validator has been enabled, remove the "cancel" task.
		// If the validator, on the last run, threw a Throwable, remove the
		// "internal error" task. (For the same reasons we remove the "cancel"
		// task.
		InternalValidatorManager.getManager().removeOperationTasks(getProject(), vmd);
		checkCanceled(reporter);
		try {
			// Check to see if a full build must be performed, or if a delta
			// build is to be performed, if there are files to verify for that
			// validator. (If it's delta, but there are no files, calling
			// validate on that validator starts a full build, instead of just
			// returning.)
			if (isFullValidate()) {
				String message = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_START_REMOVING_OLD_MESSAGES, new String[]{vmd.getValidatorDisplayName(), getProject().getName()});
				reporter.displaySubtask(message);
				reporter.removeAllMessages(validator);
				message = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_FINISH_REMOVING_OLD_MESSAGES, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
				reporter.displaySubtask(message);
			} else {
				// Don't need to check that there are deltas to validate
				// because that's already checked in isValidationNecessary
				String message = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_START_REMOVING_OLD_MESSAGES, new String[]{vmd.getValidatorDisplayName(), getProject().getName()});
				reporter.displaySubtask(message);
				for (int i = 0; i < delta.length; i++) {
					WorkbenchFileDelta fd = (WorkbenchFileDelta) delta[i];
					if (fd.getDeltaType() != IFileDelta.DELETED) {
						// If the file has been deleted, eclipse erases all
						// markers on the file.
						// Also, when a resource doesn't exist,
						// WorkbenchReporter's getMessageResource()
						// returns the IProject, which means that removing the
						// messages from this
						// file removes all of this validator's messages on
						// this IProject (aix defect 206157)
						IResource resource = reporter.getMessageResource(validator, fd);
						if (fd.getObject().equals(fd.getResource())) {
							WorkbenchReporter.removeAllMessages(resource, validator); // remove
						} else {
							reporter.removeAllMessages(validator, fd.getObject());
						}
					}
				}
				message = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_FINISH_REMOVING_OLD_MESSAGES, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
				reporter.displaySubtask(message);
			}
		} catch (OperationCanceledException exc) {
			throw exc;
		} catch (Exception e) {
			ValidationPlugin.getPlugin().handleException(e);
			String mssg = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_ENDING_VALIDATION_ABNORMALLY, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
			reporter.displaySubtask(mssg);
			return;
		}
	}

	/**
	 * Whether a full verification or a delta verification is in progress, both will call this
	 * method to process the resource. This method calls the current Validator to filter the
	 * resource (i.e., this method returns if the resource fails the filter test).
	 * <code>process</code> also sends output to the <code>IProgressMonitor</code>, and calls
	 * the current Validator to validate the resource.
	 * 
	 * <p>To process a resource, there are several steps:</p> 
	 * <p>1. check if the resource is registered for
	 * this validator (i.e., the validator has either specified it in a filter, or has not filtered
	 * it out explicitly)</p>
	 * 
	 * <p>2. call <code>isValidationSource</code> on the current validator with
	 * the current resource. This method performs further filtering by the Validator itself, in
	 * addition to the static filtering done by the framework, based on the information in
	 * plugin.xml.</p> 
	 * 
	 * <p>3. If the resource passes both filters, call <code>validate</code> on the
	 * validator, with the resource.</p> 
	 * 
	 * <p>4. When complete (either by failing to pass a filter, or by the
	 * completion of the <code>validate</code>), increment the IProgressMonitor's status by one
	 * (i.e., one resource has been processed.)</p>
	 */
	private final void launchValidator(WorkbenchReporter reporter, IValidator validator, ValidatorMetaData vmd, IWorkbenchContext helper, IFileDelta[] delta) {
		if (reporter == null)return;
		checkCanceled(reporter);
		// Check to see if a full build must be performed, or if a delta
		// build is to be performed, if there are files to verify for that
		// validator. (If it's delta, but there are no files, calling
		// validate on that validator starts a full build, instead of just
		// returning.)
		try {
			// Validate the resource; this step will add errors/warnings to the
			// task list, and remove errors/warnings from the task list.
			if (helper instanceof WorkbenchContext) {
				// Initialize the "loadRuleGroup" method with the group of rules
				// which the validator should validate.
				((WorkbenchContext) helper).setRuleGroup(getRuleGroup());
			}
			String message = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_STARTING_VALIDATION, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
			reporter.displaySubtask(message);
			if (Tracing.isLogging()) {
				// This internal "launched validators" value is used only in tests.
				getLaunchedValidators().add(vmd);
			}
			//initValidateContext(delta);
			ValidatorLauncher.getLauncher().start(helper, validator, reporter);
			//TODO GRK determine if timing info should be added here
//			if (logger.isLoggingLevel(Level.INFO)) {
//				TimeEntry entry = ValidationPlugin.getTimeEntry();
//				entry.setSourceID("ValidationOperation.launchValidator"); //$NON-NLS-1$
//				entry.setProjectName(getProject().getName());
//				entry.setToolName(vmd.getValidatorUniqueName());
//				entry.setElapsedTime(finish - start);
//				if (logger.isLoggingLevel(Level.FINE)) {
//					StringBuffer buffer = new StringBuffer();
//					if (isFullValidate()) {
//						buffer.append("EVERYTHING"); //$NON-NLS-1$
//					} else {
//						if (delta.length == 0) {
//							buffer.append("NOTHING"); //$NON-NLS-1$
//						} else {
//							buffer.append(getDeltaAsString(delta));
//						}
//					}
//					entry.setDetails(buffer.toString());
//				}
//				logger.write(Level.INFO, entry);
//			}
			message = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_ENDING_VALIDATION, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
			reporter.displaySubtask(message);
		} catch (OperationCanceledException exc) {
			throw exc;
		} catch (ValidationException exc) {
			// First, see if a validator just caught all Throwables and
			// accidentally wrapped a MessageLimitException instead of
			// propagating it.
			if (exc.getAssociatedException() != null) {
				if (exc.getAssociatedException() instanceof ValidationException) {
					ValidationException vexc = (ValidationException) exc.getAssociatedException();
					vexc.setClassLoader(validator.getClass().getClassLoader()); // first,
					// set the class loader, so that the exception's getMessage() method
					// can retrieve the resource bundle
				}
			}
			// If there is a problem with this particular validator, log the
			// error and continue with the next validator.
			exc.setClassLoader(validator.getClass().getClassLoader()); // first,
			ValidationPlugin.getPlugin().handleException(exc);
			ValidationPlugin.getPlugin().handleException(exc.getAssociatedException());
			String message = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_ENDING_VALIDATION_ABNORMALLY, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
			reporter.displaySubtask(message);
			if (exc.getAssociatedMessage() != null) {
				reporter.addMessage(validator, exc.getAssociatedMessage());
			}
		} catch (Exception e) {
			ValidationPlugin.getPlugin().handleException(e);
			String mssg = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_ENDING_VALIDATION_ABNORMALLY, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
			reporter.displaySubtask(mssg);
			
			/*
			 The code  below  causes bundle not found exception since, the  bundle here is
			 validate_base and we  are  trying to load that bundle from the classloader of 
			 the Validator. 
			 
			String[] msgParm = {exc.getClass().getName(), vmd.getValidatorDisplayName(), (exc.getMessage() == null ? "" : exc.getMessage())}; //$NON-NLS-1$
			Message message = ValidationPlugin.getMessage();
			message.setSeverity(IMessage.NORMAL_SEVERITY);
			message.setId(ResourceConstants.VBF_EXC_RUNTIME);
			message.setParams(msgParm);
			reporter.addMessage(validator, message);
			*/
			
		} finally {
			try {
				validator.cleanup(reporter);
			} catch (OperationCanceledException e) {
				throw e;
			} catch (Exception e) {
				ValidationPlugin.getPlugin().handleException(e);
				return;
			}
			try {
				helper.cleanup(reporter);
			} catch (OperationCanceledException e) {
				throw e;
			} catch (Exception e) {
				ValidationPlugin.getPlugin().handleException(e);
				return;
			} finally {
				// Now that cleanup has been called, set the project to null.
				// This project's
				// resources have been freed so this project should also be
				// cleared on the helper.
				// If it isn't set to null, then the next time that the helper
				// is retrieved from
				// the ValidatorMetaData, the resources allocated for this
				// project, in the
				// helper's initialize method, will not be reallocated if the
				// project is the same.
				helper.setProject(null);
			}
			// Tell the progress monitor that we've completed n units of work
			// (i.e., n resources validated by one validator).
			reporter.getProgressMonitor().worked(((delta == null) ? 1 : delta.length));
		}
	}

//	private Runnable wrapInRunnable(final WorkbenchReporter reporter, final IValidator validator, final ValidatorMetaData vmd, final IWorkbenchContext helper, final IFileDelta[] delta, final Iterator iterator) {
//		// Need to create a new Runnable each time because several Runnable
//		// instances may exist at the same time.
//		Runnable runnable = new ProjectRunnable(reporter, validator, vmd, helper, delta, iterator);
//		return runnable;
//	}

	/*
	 * // For convenience, keep this method in the class but commented out. // When async needs to
	 * be tested, this method may be needed again. private static void debug(String prefix,
	 * IWorkbenchContext helper) { IProject hProject = helper.getProject(); System.err.println(prefix +
	 * "Start ValidationOperation "+Thread.currentThread().getName() + "::" + hProject.getName());
	 * if( Thread.currentThread().getName().equals("ValidationThread") &&
	 * (hProject.getName().indexOf("noFork") > -1)) { Thread.dumpStack(); } else
	 * if((!Thread.currentThread().getName().equals("ValidationThread")) &&
	 * (hProject.getName().indexOf("fork") > -1)) { Thread.dumpStack(); } System.err.println(prefix +
	 * "End ValidationOperation"); }
	 */
	
	/**
	 * @deprecated This class is no longer used by the framework.
	 */
	public class ProjectRunnable implements Runnable {
		private WorkbenchReporter _reporter;
		private IValidator _validator;
		private ValidatorMetaData _vmd;
		private IFileDelta[] _delta;

		@SuppressWarnings("unchecked")
		public ProjectRunnable(WorkbenchReporter reporter, IValidator validator, 
			ValidatorMetaData vmd, IWorkbenchContext helper, IFileDelta[] delta, Iterator iterator) {
			_reporter = reporter;
			_validator = validator;
			_vmd = vmd;
			_delta = delta;
		}

		public void run() {
			try {
				internalValidate(_reporter, _validator, _vmd, context,_delta);
			} catch (OperationCanceledException exc) {
				// User can't cancel a job in a background thread, so ignore this exception.
			}
		}

		public IProject getProject() {
			return _reporter.getProject();
		}
	}

	/**
	 * @return Returns the context.
	 */
	public IValidationContext getContext() {
		return context;
	}

	/**
	 * @param context The context to set.
	 */
	public void setContext(IWorkbenchContext context) {
		this.context = context;
	}
	
	void launchJobs(Set<ValidatorMetaData> validators, final WorkbenchReporter reporter) throws OperationCanceledException{
		
		IValidator validator = null;
		IFileDelta[] delta = null;
		IWorkbenchContext workbenchcontext = null;
		
		for (ValidatorMetaData vmd : validators) {
			checkCanceled(reporter);
			
			try {
				delta = getFileDeltas(reporter.getProgressMonitor(), vmd);
				boolean willRun = (isForce() || isValidationNecessary(vmd, delta));
				if (!willRun)continue;

			} catch (CoreException e) {
				ValidationPlugin.getPlugin().handleException(e);
				String mssg = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_ENDING_VALIDATION_ABNORMALLY, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
				reporter.displaySubtask(mssg);
				continue;
			}
			
			try {
				validator = vmd.createValidator();
				workbenchcontext = vmd.createHelper( getProject() );
				initValidateContext( delta, workbenchcontext );
				vmd.addHelper((IValidatorJob)validator, workbenchcontext);				
				checkCanceled(reporter);
				
			} catch (InstantiationException e) {
				// Remove the vmd from the reader's list
				ValidationRegistryReader.getReader().disableValidator(vmd);
				ValidationPlugin.getPlugin().handleException(e);
				continue;
			}
			
			try {
				checkCanceled(reporter);
				removeOldMessages(reporter, validator, vmd, delta);
				
				if( validator instanceof IValidatorJob ){
					launchValidatorJob( reporter, (IValidatorJob)validator, vmd, workbenchcontext, delta);
				}

				
			} catch (OperationCanceledException exc) {
				throw exc;

			} catch (Exception e) {
				ValidationPlugin.getPlugin().handleException(e);
				String mssg = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_ENDING_VALIDATION_ABNORMALLY, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
				reporter.displaySubtask(mssg);
			} catch (Error e) {
				ValidationPlugin.getPlugin().handleException(e);
				String mssg = ResourceHandler.getExternalizedMessage(ResourceConstants.VBF_STATUS_ENDING_VALIDATION_ABNORMALLY, new String[]{getProject().getName(), vmd.getValidatorDisplayName()});
				reporter.displaySubtask(mssg);
				throw e;
			} finally {
				// If user fixes problem, and limit exceeded, add "exceeded"
				// message, or if limit not exceeded any more, remove "exceeded" message.
				reporter.getProgressMonitor().done();
			}
		}
	}
		
	private void initValidateContext(IFileDelta[] delta, IWorkbenchContext context ) {
		 if (context instanceof WorkbenchContext) {
			 ((WorkbenchContext)context).setValidationFileURIs(new ArrayList<String>());
			 for(IFileDelta file : delta) {
				 if(file.getDeltaType() != IFileDelta.DELETED ) {
					 ((WorkbenchContext)context).getValidationFileURIs().add(file.getFileName());
				 }
			 } 
		}
	}
						
	private final void launchValidatorJob(WorkbenchReporter reporter,
		IValidatorJob validator, ValidatorMetaData vmd, IWorkbenchContext helper, IFileDelta[] delta) {
		
		if (reporter == null)return;
		checkCanceled(reporter);

		if (helper instanceof WorkbenchContext) {
			((WorkbenchContext) helper).setRuleGroup(getRuleGroup());
		}
		if (Tracing.isLogging()) {
			// This internal "launched validators" value is used only in tests.
			getLaunchedValidators().add(vmd);
		}
		
		ValidatorJob validatorjob = new ValidatorJob( validator, vmd.getValidatorDisplayName(), 
			vmd.getValidatorUniqueName(), helper.getProject(), helper );

		ISchedulingRule schedulingRule = validator.getSchedulingRule(helper);
		validatorjob.setRule( schedulingRule );		
		QualifiedName validatorKey = new QualifiedName(null, "Validator"); //$NON-NLS-1$
		validatorjob.setProperty( validatorKey, validator );
		validatorjob.addJobChangeListener(
			new JobChangeAdapter(){
				
				public void done(IJobChangeEvent event){
					Job job = event.getJob();
					QualifiedName validatorKey = new QualifiedName(null, "Validator"); //$NON-NLS-1$
					IValidatorJob validator = (IValidatorJob)job.getProperty( validatorKey );
					ValidatorManager mgr = ValidatorManager.getManager();
					final List<MessageInfo> list = mgr.getMessages(validator);							
					
					IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
					    public void run(IProgressMonitor monitor) throws CoreException {

							for(MessageInfo info : list){
								try {								
									TaskListUtility.addTask( info.getMessageOwnerId(), info.getResource(),
										info.getLocation(), info.getMsg().getId(), info.getText(),
										info.getMsg().getSeverity(),
										info.getMarkerId(),
										info.getTargetObjectName(),
										info.getMsg().getGroupName(),
										info.getMsg().getOffset(),
										info.getMsg().getLength());								
								} catch (CoreException e) {
									ValidationPlugin.getPlugin().handleException(e);
								}										
							}
					    }
					};
					try {
						
						ResourcesPlugin.getWorkspace().run(runnable, null, IWorkspace.AVOID_UPDATE, null);
					} catch (CoreException e) {
					   e.printStackTrace();
					}
					mgr.clearMessages( validator );
					validator = null;
				}
			}
		);
		validatorjob.setPriority(Job.DECORATE);
		launcherJob.addValidationJob(validatorjob);
	}
		
}
