/*******************************************************************************
* 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.text.MessageFormat;
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.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
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.ui.texteditor.MarkerAnnotation;
import org.eclipse.wst.sse.ui.internal.reconcile.TemporaryAnnotation;
import org.eclipse.wst.validation.internal.core.Message;
import org.eclipse.wst.validation.internal.provisional.core.IMessage;
import org.eclipse.wst.validation.internal.provisional.core.IReporter;
import org.eclipse.wst.validation.internal.provisional.core.IValidator;
import org.jboss.tools.common.CommonPlugin;
import org.jboss.tools.common.quickfix.MarkerAnnotationInfo;
import org.jboss.tools.common.text.ITextSourceReference;
import org.jboss.tools.common.util.EclipseUIUtil;
/**
* @author Alexey Kazakov
*/
abstract public class TempMarkerManager extends ValidationErrorManager {
public static final String MESSAGE_TYPE_ATTRIBUTE_NAME = MarkerAnnotationInfo.MESSAGE_TYPE_ATTRIBUTE_NAME;
protected boolean asYouTypeValidation;
protected int messageCounter;
private static final IMessage[] EMPTY_MESSAGE_ARRAY = new IMessage[0];
protected abstract String getMessageBundleName();
/*
* (non-Javadoc)
* @see org.jboss.tools.common.validation.ValidationErrorManager#init(org.eclipse.core.resources.IProject, org.jboss.tools.common.validation.ContextValidationHelper, org.jboss.tools.common.validation.IProjectValidationContext, org.eclipse.wst.validation.internal.provisional.core.IValidator, org.eclipse.wst.validation.internal.provisional.core.IReporter, boolean)
*/
@Override
public void init(IProject project, ContextValidationHelper validationHelper, IProjectValidationContext validationContext, IValidator manager, IReporter reporter, boolean asYouTypeValidation) {
super.init(project, validationHelper, validationContext, manager, reporter, asYouTypeValidation);
messageCounter = 0;
}
/**
* @return the asYouTypeValidation
*/
public boolean isAsYouTypeValidation() {
return asYouTypeValidation;
}
/**
* @param asYouTypeValidation the asYouTypeValidation to set
*/
public void setAsYouTypeValidation(boolean asYouTypeValidation) {
this.asYouTypeValidation = asYouTypeValidation;
dirtyFiles = asYouTypeValidation?new HashSet<IFile>():EclipseUIUtil.getDirtyFiles();
}
public void addProblem(String message, String preferenceKey, ITextSourceReference location, IResource target) {
if(asYouTypeValidation) {
addMessage(target, location, preferenceKey, message);
} else {
addError(message, preferenceKey, location, target);
}
}
public void addProblem(String message, String preferenceKey, String[] messageArguments, ITextSourceReference location, IResource target) {
if(asYouTypeValidation) {
addMessage(target, location, preferenceKey, message, messageArguments);
} else {
addError(message, preferenceKey, messageArguments, location, target);
}
}
public void addProblem(String message, String preferenceKey, ITextSourceReference location, IResource target, Integer quickFixId) {
if(asYouTypeValidation) {
addMessage(target, location, preferenceKey, message, quickFixId);
} else {
addError(message, preferenceKey, location, target, quickFixId);
}
}
public IMarker addProblem(String message, String preferenceKey, String[] messageArguments, int length, int offset, IResource target, Integer quickFixId) {
if(asYouTypeValidation) {
addMessage(target, offset, length, preferenceKey, message, messageArguments, quickFixId);
return null;
} else {
return addError(message, preferenceKey, messageArguments, length, offset, target, quickFixId);
}
}
public IMarker addProblem(String message, String preferenceKey, String[] messageArguments, int length, int offset, IResource target) {
if(asYouTypeValidation) {
addMessage(target, offset, length, preferenceKey, message, messageArguments);
return null;
} else {
return addError(message, preferenceKey, messageArguments, length, offset, target);
}
}
public IMessage addMessage(IResource target, ITextSourceReference location, String preferenceKey, String textMessage) {
return addMessage(target, -1, location, preferenceKey, textMessage, null);
}
public IMessage addMessage(IResource target, ITextSourceReference location, String preferenceKey, String textMessage, String[] messageArguments) {
return addMessage(target, -1, location, preferenceKey, textMessage, messageArguments);
}
public IMessage addMessage(IResource target, ITextSourceReference location, String preferenceKey, String textMessage, int quickFixId) {
IMessage message = addMessage(target, -1, location, preferenceKey, textMessage, null);
if(message!=null && quickFixId != -1) {
message.setAttribute(MESSAGE_ID_ATTRIBUTE_NAME, quickFixId);
}
return message;
}
public IMessage addMessage(IResource target, int lineNumber, ITextSourceReference location, String preferenceKey, String textMessage, String[] messageArguments) {
IMessage message = null;
IResource actualTarget = location.getResource();
if(target.equals(actualTarget)) {
int severity = getSeverity(preferenceKey, target);
try {
if(severity!=-1 && (severity!=IMessage.NORMAL_SEVERITY || !hasSuppressWarningsAnnotation(preferenceKey, location))) {
message = addMesssage(target, lineNumber, location.getStartPosition(), location.getLength(), severity, preferenceKey, textMessage, messageArguments, -1);
}
} catch (JavaModelException e) {
CommonPlugin.getDefault().logError(e);
}
}
return message;
}
public IMessage addMessage(IResource target, int offset, int length, String preferenceKey, String messageText, String[] messageArguments, int quickFixId) {
return addMessage(target, -1, offset, length, preferenceKey, messageText, messageArguments, quickFixId);
}
public IMessage addMessage(IResource target, int offset, int length, String preferenceKey, String message, String[] messageArguments) {
return addMessage(target, -1, offset, length, preferenceKey, message, messageArguments, -1);
}
public IMessage addMessage(IResource target, int lineNumber, int offset, int length, String preferenceKey, String message, String[] messageArguments, int quickFixId) {
int severity = getSeverity(preferenceKey, target);
return severity!=-1?addMesssage(target, lineNumber, offset, length, severity, preferenceKey, message, messageArguments, quickFixId):null;
}
private IMessage addMesssage(IResource target, int lineNumber, int offset, int length, int severity, String preferenceKey, String textMessage, String[] messageArguments, int quickFixId) {
IMessage message = null;
if(messageCounter<=getMaxNumberOfMarkersPerFile(target.getProject())) {
if(lineNumber<0) {
try {
lineNumber = document.getLineOfOffset(offset) + 1;
} catch (BadLocationException e) {
CommonPlugin.getDefault().logError(e);
}
}
message = addMesssage(validationManager, shouldCleanAllAnnotations(), this.reporter, offset, length, target, lineNumber, severity, textMessage, messageArguments, getMessageBundleName());
if(message!=null && quickFixId != -1) {
message.setAttribute(MESSAGE_ID_ATTRIBUTE_NAME, quickFixId);
}
messageCounter++;
if(preferenceKey != null){
message.setAttribute(PREFERENCE_KEY_ATTRIBUTE_NAME, preferenceKey);
}
String type = getProblemType();
if(type!=null) {
message.setAttribute(MESSAGE_TYPE_ATTRIBUTE_NAME, type);
}
message.setAttribute(VALIDATOR_ID_ATTRIBUTE, getId());
if(asYouTypeTimestamp != 0) {
message.setAttribute(VALIDATOR_TIMESTAMP_ATTRIBUTE, new Integer(asYouTypeTimestamp));
}
}
return message;
}
/**
* Returns id of validator. May not return null.
* @return
*/
public abstract String getId();
protected int asYouTypeTimestamp = 0;
/**
* Returns true if all the annotations belonged to this validator of entire document should be removed before start this validator again.
* If "false" then only annotations of the changed region should be removed.
* Returns "false" by default but subclasses are free to override this method.
* @return
*/
protected boolean shouldCleanAllAnnotations() {
return false;
}
public static final String AS_YOU_TYPE_VALIDATION_ANNOTATION_ATTRIBUTE = "org.jboss.tools.common.validation.asyoutype";
public static final String CLEAN_ALL_ANNOTATIONS_ATTRIBUTE = "org.jboss.tools.common.validation.cleanAllAnnotaions";
public static final String VALIDATOR_ID_ATTRIBUTE = "org.jboss.tools.common.validation.id";
public static final String VALIDATOR_TIMESTAMP_ATTRIBUTE = "org.jboss.tools.common.validation.timestamp";
private static IMessage addMesssage(IValidator validator, boolean cleanAllAnnotaions, IReporter reporter, int offset, int length, IResource target, int lineNumber, int severity, String textMessage, Object[] messageArguments, String bundleName) {
if(messageArguments==null) {
messageArguments = new String[0];
}
Message message = new ValidationMessage(severity, MessageFormat.format(textMessage, messageArguments), target);
message.setOffset(offset);
message.setLength(length);
message.setLineNo(lineNumber);
message.setBundleName(bundleName);
message.setAttribute(AS_YOU_TYPE_VALIDATION_ANNOTATION_ATTRIBUTE, Boolean.TRUE);
if(cleanAllAnnotaions) {
message.setAttribute(CLEAN_ALL_ANNOTATIONS_ATTRIBUTE, Boolean.TRUE);
}
reporter.addMessage(validator, message);
return message;
}
@Deprecated // Probably a useful class, but not used at the moment
private static class SimpleReference implements ITextSourceReference {
int start;
int length;
IResource resource;
public SimpleReference(int start, int length, IResource resource) {
this.start = start;
this.length = length;
this.resource = resource;
}
@Override
public int getStartPosition() {
return start;
}
@Override
public int getLength() {
return length;
}
@Override
public IResource getResource() {
return resource;
}
};
protected void disableProblemAnnotations(final IRegion region, IReporter reporter) {
List messages = reporter.getMessages();
IMessage[] msgs = EMPTY_MESSAGE_ARRAY;
if(messages!=null) {
msgs = (IMessage[])messages.toArray(new IMessage[messages.size()]);
}
int ts = 0;
if(msgs.length > 0) {
Object o = msgs[0].getAttribute(VALIDATOR_TIMESTAMP_ATTRIBUTE);
if(o instanceof Integer) {
ts = ((Integer)o).intValue();
}
}
final IMessage[] messageArray = msgs;
final int _timestamp = ts;
UIJob job = new UIJob("As-you-type JBT validation. Disabling the marker annotations.") {
public IStatus runInUIThread(IProgressMonitor monitor) {
if(EclipseUIUtil.isActiveEditorDirty()) {
ITextEditor e = EclipseUIUtil.getActiveEditor();
if(e!=null) {
IEditorInput input = e.getEditorInput();
IDocumentProvider dp = e.getDocumentProvider();
if(document == dp.getDocument(input)) {
IAnnotationModel model = dp.getAnnotationModel(input);
if(model instanceof AbstractMarkerAnnotationModel) {
AbstractMarkerAnnotationModel anModel = ((AbstractMarkerAnnotationModel)model);
synchronized (anModel.getLockObject()) {
Iterator iterator = anModel.getAnnotationIterator(region.getOffset(), region.getLength(), true, true);
Set<Annotation> annotationsToRemove = new HashSet<Annotation>();
Map<Annotation, Position> newAnnotations = new HashMap<Annotation, Position>();
while (iterator.hasNext()) {
Object o = iterator.next();
if(o instanceof MarkerAnnotation) {
MarkerAnnotation annotation = (MarkerAnnotation)o;
IMarker marker = annotation.getMarker();
try {
String type = marker.getType();
if(getProblemType().equals(type)) {
int offset = marker.getAttribute(IMarker.CHAR_START, 0);
int originalMarkerEnd = marker.getAttribute(IMarker.CHAR_END, -1);
String markerMessage = marker.getAttribute(IMarker.MESSAGE, "");
boolean removedProblem = true;
for (Object object : messageArray) {
IMessage message = (IMessage)object;
if(message.getOffset() == offset && message.getLength() == originalMarkerEnd - offset && markerMessage.equals(message.getText())) {
removedProblem = false;
break;
}
}
if(removedProblem) {
Annotation newAnnotation = new DisabledAnnotation(annotation, type, marker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_WARNING) == IMarker.SEVERITY_WARNING);
Position p = anModel.getPosition(annotation);
newAnnotations.put(newAnnotation, p);
annotationsToRemove.add(annotation);
} else {
annotation.markDeleted(true);
}
} else if("org.jboss.tools.jst.jsp.jspeditor.JSPMultiPageEditor".equals(e.getClass().getName()) && "org.eclipse.jst.jsf.facelet.ui.FaceletValidationMarker".equals(type)) {
// Remove WTP's annotations for JBT JSP/XHTML editors.
annotationsToRemove.add(annotation);
}
} catch (CoreException ce) {
CommonPlugin.getDefault().logError(ce);
}
} else if(o instanceof DisabledAnnotation) {
DisabledAnnotation annotation = (DisabledAnnotation)o;
Position p = anModel.getPosition(annotation);
for (Object object : messageArray) {
IMessage message = (IMessage)object;
if(getProblemType().equals(annotation.getProblemType()) && message.getOffset() == p.getOffset() && annotation.getText().equals(message.getText())) {
annotationsToRemove.add(annotation);
Annotation markerAnnotation = annotation.getOverlaidAnnotation();
markerAnnotation.markDeleted(true);
Position newPossition = new Position(message.getOffset(), message.getLength());
newAnnotations.put(markerAnnotation, newPossition);
break;
}
}
} else if(o instanceof TemporaryAnnotation) {
TemporaryAnnotation annotation = (TemporaryAnnotation)o;
Map<?,?> attributes = annotation.getAttributes();
Object validatorId = attributes == null ? null : attributes.get(VALIDATOR_ID_ATTRIBUTE);
if(validatorId != null && validatorId.equals(getId())) {
Object ts = attributes.get(VALIDATOR_TIMESTAMP_ATTRIBUTE);
if(ts instanceof Integer && ((Integer)ts).intValue() < _timestamp) {
annotationsToRemove.add(annotation);
}
}
}
}
// if(!newAnnotations.isEmpty()) {
// Annotation[] annotationsToRemoveArray = annotationsToRemove.toArray(new Annotation[annotationsToRemove.size()]);
// anModel.replaceAnnotations(annotationsToRemoveArray, newAnnotations);
// }
for (Annotation annotation : annotationsToRemove) {
anModel.removeAnnotation(annotation);
}
for (Annotation annotation : newAnnotations.keySet()) {
anModel.addAnnotation(annotation, newAnnotations.get(annotation));
}
}
}
}
}
}
return Status.OK_STATUS;
}
};
job.schedule();
}
}