/*******************************************************************************
* Copyright (c) 2012 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.common.validation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.texteditor.AbstractMarkerAnnotationModel;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.wst.sse.ui.internal.reconcile.TemporaryAnnotation;
import org.eclipse.wst.sse.ui.internal.reconcile.validator.ISourceValidator;
import org.eclipse.wst.validation.internal.core.ValidationException;
import org.eclipse.wst.validation.internal.provisional.core.IReporter;
import org.eclipse.wst.validation.internal.provisional.core.IValidationContext;
import org.jboss.tools.common.CommonPlugin;
import org.jboss.tools.common.util.EclipseUIUtil;
import org.jboss.tools.common.validation.java.TempJavaProblemAnnotation;
/**
* This Manager is responsible for as-you-type validation.
* It's registred as WTP source validator and delegates validation to the corresponding JBT validators.
* @author Alexey Kazakov
*/
public class AsYouTypeValidatorManager implements ISourceValidator, org.eclipse.wst.validation.internal.provisional.core.IValidator {
private IDocument document;
private IFile file;
private EditorValidationContext context;
private Map<IValidator, IProject> rootProjects;
private int count;
private static boolean disabled;
private boolean disconnected = true;
private static Set<IDocument> reporters = new HashSet<IDocument>();
/*
* (non-Javadoc)
* @see org.eclipse.wst.sse.ui.internal.reconcile.validator.ISourceValidator#connect(org.eclipse.jface.text.IDocument)
*/
@Override
public void connect(IDocument document) {
synchronized(this) {
disconnected = false;
count = 0;
this.document = document;
}
}
/*
* (non-Javadoc)
* @see org.eclipse.wst.sse.ui.internal.reconcile.validator.ISourceValidator#disconnect(org.eclipse.jface.text.IDocument)
*/
@Override
public void disconnect(IDocument document) {
synchronized (this) {
disconnected = true;
context = null;
synchronized (reporters) {
reporters.remove(document);
}
}
}
/**
* Remove all the temporary annotations added by this validator to the the active not dirty editor.
*/
static void removeMessages() {
UIJob job = new UIJob("Removing as-you-type JBT validation problems") {
public IStatus runInUIThread(IProgressMonitor monitor) {
if(!EclipseUIUtil.isActiveEditorDirty()) {
ITextEditor e = EclipseUIUtil.getActiveEditor();
if(e!=null) {
IEditorInput input = e.getEditorInput();
IDocumentProvider dp = e.getDocumentProvider();
IDocument doc = dp.getDocument(input);
boolean ok = false;
synchronized (reporters) {
ok = reporters.contains(doc);
}
if(ok) {
IAnnotationModel model = dp.getAnnotationModel(input);
if(model instanceof AbstractMarkerAnnotationModel) {
AbstractMarkerAnnotationModel anModel = ((AbstractMarkerAnnotationModel)model);
synchronized (anModel.getLockObject()) {
Iterator iterator = anModel.getAnnotationIterator();
while (iterator.hasNext()) {
Object o = iterator.next();
if(o instanceof TemporaryAnnotation) {
TemporaryAnnotation annotation = (TemporaryAnnotation)o;
Map attributes = annotation.getAttributes();
if(attributes!=null && attributes.get(TempMarkerManager.AS_YOU_TYPE_VALIDATION_ANNOTATION_ATTRIBUTE)!=null) {
anModel.removeAnnotation(annotation);
}
} else if(o instanceof DisabledAnnotation) {
DisabledAnnotation annotation = (DisabledAnnotation)o;
anModel.removeAnnotation(annotation);
} else if(o instanceof TempJavaProblemAnnotation) {
TempJavaProblemAnnotation annotation = (TempJavaProblemAnnotation)o;
anModel.removeAnnotation(annotation);
}
}
}
}
}
}
}
return Status.OK_STATUS;
}
};
job.schedule();
}
private boolean init(IValidationContext helper, IReporter reporter, boolean test) {
if(!test && disabled) {
return false;
}
if(context==null) {
synchronized (reporters) {
reporters.add(document);
}
String[] uris = helper.getURIs();
if(uris.length==0) {
return false;
}
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
file = root.getFile(new Path(uris[0]));
if(!file.isAccessible()) {
return false;
}
context = new EditorValidationContext(file.getProject(), document);
if(context.getValidators().isEmpty()) {
return false;
}
rootProjects = new HashMap<IValidator, IProject>();
for (IValidator validator : context.getValidators()) {
Map<IProject, IValidatingProjectSet> projectTree = context.getValidatingProjectTree(validator).getBrunches();
if(!projectTree.isEmpty()) {
IProject rootProject = projectTree.keySet().iterator().next();
rootProjects.put(validator, rootProject);
}
}
}
return true;
}
private void validate(Set<? extends IAsYouTypeValidator> validators, Collection<IRegion> dirtyRegions, IValidationContext helper, IReporter reporter) {
count++;
for (IAsYouTypeValidator validator : validators) {
try {
if(context==null) { // The manager has been disposed. Stop validation.
break;
}
IProject rootProject = rootProjects.get(validator);
IValidatingProjectSet projectBrunch = context.getValidatingProjectTree(validator).
getBrunches().
get(rootProject);
if(projectBrunch!=null) {
validator.validate(this, rootProject, dirtyRegions, helper, reporter, context, projectBrunch.getRootContext(), file);
}
} catch(Exception e) {
// We need to catch exceptions and wrap them in KBValidationException to let JUnit tests catch validation exceptions reported to eclipse log.
CommonPlugin.getDefault().logError(new JBTValidationException(e.getMessage(), e));
}
}
}
/**
* Validate the string
*
* @param dirtyRegion
* @param helper
* @param reporter
*/
public void validateString(Collection<IRegion> dirtyRegions, IValidationContext helper, IReporter reporter) {
validateString(dirtyRegions, helper, reporter, false);
}
protected void validateString(Collection<IRegion> dirtyRegions, IValidationContext helper, IReporter reporter, boolean test) {
if(shouldValidate(helper, reporter, test)) {
validate(context.getStringValidators(), dirtyRegions, helper, reporter);
}
}
/**
* Validate the java element
*
* @param dirtyRegion
* @param helper
* @param reporter
*/
public void validateJavaElement(Collection<IRegion> dirtyRegions, IValidationContext helper, IReporter reporter) {
validateJavaElement(dirtyRegions, helper, reporter, false);
}
protected void validateJavaElement(Collection<IRegion> dirtyRegions, IValidationContext helper, IReporter reporter, boolean test) {
if(shouldValidate(helper, reporter, test)) {
validate(context.getJavaElementValidators(), dirtyRegions, helper, reporter);
}
}
private boolean shouldValidate(IValidationContext helper, IReporter reporter, boolean test) {
boolean ok = false;
synchronized (this) {
ok = !disconnected && init(helper, reporter, test);
}
return ok;
}
/*
* (non-Javadoc)
* @see org.eclipse.wst.sse.ui.internal.reconcile.validator.ISourceValidator#validate(org.eclipse.jface.text.IRegion, org.eclipse.wst.validation.internal.provisional.core.IValidationContext, org.eclipse.wst.validation.internal.provisional.core.IReporter)
*/
@Override
public void validate(IRegion dirtyRegion, IValidationContext helper, IReporter reporter) {
List<IRegion> regions = null;
synchronized (this) {
if(!disconnected) {
if(count==0) {
// Don't validate the file first time since WTP invokes the validator right after connection.
init(helper, reporter, false);
count++;
} else {
regions = new ArrayList<IRegion>();
regions.add(dirtyRegion);
}
}
}
if(regions!=null) {
validateString(regions, helper, reporter);
}
}
@Override
public void cleanup(IReporter reporter) {
}
@Override
public void validate(IValidationContext helper, IReporter reporter) throws ValidationException {
}
public static boolean isDisabled() {
return disabled;
}
/**
* Disable As-you-type validation
* @param disabled
*/
public static void setDisabled(boolean disabled) {
AsYouTypeValidatorManager.disabled = disabled;
}
}