package org.python.pydev.shared_ui.mark_occurrences;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.AssertionFailedException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.ProgressMonitorWrapper;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.ISynchronizable;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.python.pydev.shared_core.log.Log;
import org.python.pydev.shared_core.string.TextSelectionUtils;
import org.python.pydev.shared_ui.editor.BaseEditor;
/**
* This is a 'low-priority' thread. It acts as a singleton. Requests to mark the occurrences
* will be forwarded to it, so, it should sleep for a while and then check for a request.
*
* If the request actually happened, it will go on to process it, otherwise it will sleep some more.
*
* @author Fabio
*/
public abstract class BaseMarkOccurrencesJob extends Job {
protected static class MarkOccurrencesRequest {
public final boolean proceedWithMarkOccurrences;
public MarkOccurrencesRequest(boolean proceedWithMarkOccurrences) {
this.proceedWithMarkOccurrences = proceedWithMarkOccurrences;
}
}
public static final boolean DEBUG = false;
public BaseMarkOccurrencesJob(String string) {
super(string);
}
/**
* This is the editor to be analyzed
*/
protected WeakReference<BaseEditor> editor;
/**
* This is the request time for this job
*/
private long currRequestTime = -1;
/**
* Make it thread safe.
*
* Note: it's static because we only want 1 mark occurrences job running at a time!
*/
private static volatile long lastRequestTime = -1;
private static BaseMarkOccurrencesJob currRunningInstance;
private static final Object lock = new Object();
public static synchronized void scheduleRequest(BaseMarkOccurrencesJob newJob) {
scheduleRequest(newJob, 700);
}
/**
* This is the function that should be called when we want to schedule a request for
* a mark occurrences job.
*/
public static synchronized void scheduleRequest(BaseMarkOccurrencesJob newJob, int scheduleTime) {
synchronized (lock) {
BaseMarkOccurrencesJob j = currRunningInstance;
if (j != null) {
//I.e.: we only want to have one job running at a time!
j.cancel();
currRunningInstance = null;
}
currRunningInstance = newJob;
currRunningInstance.schedule(scheduleTime);
}
}
/**
* The selection when the occurrences job was requested
*/
protected TextSelectionUtils ps;
protected BaseMarkOccurrencesJob(WeakReference<BaseEditor> editor, TextSelectionUtils ps) {
super("MarkOccurrencesJob");
setPriority(Job.BUILD);
setSystem(true);
this.editor = editor;
this.ps = ps;
currRequestTime = System.currentTimeMillis();
}
protected abstract MarkOccurrencesRequest createRequest(BaseEditor baseEditor,
IDocumentProvider documentProvider, IProgressMonitor monitor) throws Exception;
@Override
public IStatus run(IProgressMonitor monitor) {
if (currRequestTime == -1) {
return Status.OK_STATUS;
}
if (currRequestTime == lastRequestTime) {
return Status.OK_STATUS;
}
lastRequestTime = currRequestTime;
monitor = new ProgressMonitorWrapper(monitor) {
@Override
public boolean isCanceled() {
return super.isCanceled() || currRequestTime != lastRequestTime;
}
};
BaseEditor baseEditor = editor.get();
try {
try {
if (baseEditor == null || monitor.isCanceled()) {
return Status.OK_STATUS;
}
IEditorInput editorInput = baseEditor.getEditorInput();
if (editorInput == null) {
return Status.OK_STATUS;
}
IDocumentProvider documentProvider = baseEditor.getDocumentProvider();
if (documentProvider == null || monitor.isCanceled()) {
return Status.OK_STATUS;
}
IAnnotationModel annotationModel = documentProvider.getAnnotationModel(baseEditor.getEditorInput());
if (annotationModel == null || monitor.isCanceled()) {
return Status.OK_STATUS;
}
//now, let's see if the editor still has a document (so that we still can add stuff to it)
if (documentProvider.getDocument(editorInput) == null) {
return Status.OK_STATUS;
}
if (baseEditor.getSelectionProvider() == null) {
return Status.OK_STATUS;
}
//to see if a new request was not created in the meantime (in which case this one will be cancelled)
if (monitor.isCanceled()) {
return Status.OK_STATUS;
}
MarkOccurrencesRequest ret = createRequest(baseEditor, documentProvider, monitor);
if (baseEditor.cache == null || monitor.isCanceled()) { //disposed (cannot add or remove annotations)
return Status.OK_STATUS;
}
if (ret != null && ret.proceedWithMarkOccurrences) {
Map<String, Object> cache = baseEditor.cache;
if (cache == null) {
return Status.OK_STATUS;
}
Map<Annotation, Position> annotationsToAddAsMap = getAnnotationsToAddAsMap(baseEditor,
annotationModel,
ret, monitor);
if (annotationsToAddAsMap == null) {
//something went wrong, so, let's remove the occurrences
removeOccurenceAnnotations(annotationModel, baseEditor);
} else {
//get the ones to remove
List<Annotation> toRemove = getOccurrenceAnnotationsInEditor(baseEditor);
//let other threads execute before getting the lock on the annotation model
Thread.yield();
Thread thread = Thread.currentThread();
int initiaThreadlPriority = thread.getPriority();
try {
//before getting the lock, let's execute with normal priority, to optimize the time that we'll
//retain that object locked (the annotation model is used on lots of places, so, retaining the lock
//on it on a minimum priority thread is not a good thing.
thread.setPriority(Thread.NORM_PRIORITY);
synchronized (getLockObject(annotationModel)) {
//replace them
IAnnotationModelExtension ext = (IAnnotationModelExtension) annotationModel;
ext.replaceAnnotations(toRemove.toArray(new Annotation[0]), annotationsToAddAsMap);
}
} finally {
thread.setPriority(initiaThreadlPriority);
}
//put them in the pyEdit
cache.put(getOccurrenceAnnotationsCacheKey(),
new ArrayList<Annotation>(annotationsToAddAsMap.keySet()));
}
} else {
removeOccurenceAnnotations(annotationModel, baseEditor);
}
} catch (OperationCanceledException e) {
throw e;//rethrow this error...
} catch (AssertionFailedException e) {
String message = e.getMessage();
if (message != null && message.indexOf("The file:") != -1 && message.indexOf("does not exist.") != -1) {
//don't even report it (the file was probably removed while we were doing the analysis)
} else {
Log.log(e);
Log.log("Error while analyzing the file:" + baseEditor.getIFile());
}
} catch (Throwable initialE) {
//Totally ignore this one
// Throwable e = initialE;
// int i = 0;
// while(e.getCause() != null && e.getCause() != e && i < 30){
// e = e.getCause();
// i++;//safeguard for recursion
// }
// if(e instanceof BadLocationException){
// //ignore (may have changed during the analysis)
// }else{
// Log.log(initialE);
// Log.log("Error while analyzing the file:"+pyEdit.getIFile());
// }
}
} catch (Throwable e) {
// Log.log(e); -- ok, remove this log, as things can happen if the user starts editing after the analysis is requested
}
return Status.OK_STATUS;
}
protected abstract Map<Annotation, Position> getAnnotationsToAddAsMap(BaseEditor baseEditor,
IAnnotationModel annotationModel, MarkOccurrencesRequest ret, IProgressMonitor monitor)
throws BadLocationException;
/**
* Gotten from JavaEditor#getLockObject
*/
protected Object getLockObject(IAnnotationModel annotationModel) {
if (annotationModel instanceof ISynchronizable) {
return ((ISynchronizable) annotationModel).getLockObject();
} else {
return annotationModel;
}
}
protected abstract String getOccurrenceAnnotationsCacheKey();
protected abstract String getOccurrenceAnnotationsType();
/**
* @return the list of occurrence annotations in the pyedit
*/
public final List<Annotation> getOccurrenceAnnotationsInEditor(final BaseEditor baseEditor) {
List<Annotation> toRemove = new ArrayList<Annotation>();
final Map<String, Object> cache = baseEditor.cache;
if (cache == null) {
return toRemove;
}
@SuppressWarnings("unchecked")
List<Annotation> inEdit = (List<Annotation>) cache.get(getOccurrenceAnnotationsCacheKey());
if (inEdit != null) {
Iterator<Annotation> annotationIterator = inEdit.iterator();
while (annotationIterator.hasNext()) {
Annotation annotation = annotationIterator.next();
if (annotation.getType().equals(getOccurrenceAnnotationsType())) {
toRemove.add(annotation);
}
}
}
return toRemove;
}
/**
* @param annotationModel
*/
protected synchronized void removeOccurenceAnnotations(IAnnotationModel annotationModel, BaseEditor pyEdit) {
//remove the annotations
Map<String, Object> cache = pyEdit.cache;
if (cache == null) {
return;
}
//let other threads execute before getting the lock on the annotation model
Thread.yield();
Thread thread = Thread.currentThread();
int initiaThreadlPriority = thread.getPriority();
//before getting the lock, let's execute with normal priority, to optimize the time that we'll
//retain that object locked (the annotation model is used on lots of places, so, retaining the lock
//on it on a minimum priority thread is not a good thing.
thread.setPriority(Thread.NORM_PRIORITY);
try {
synchronized (getLockObject(annotationModel)) {
List<Annotation> annotationsToRemove = getOccurrenceAnnotationsInEditor(pyEdit);
if (annotationModel instanceof IAnnotationModelExtension) {
//replace those
((IAnnotationModelExtension) annotationModel).replaceAnnotations(
annotationsToRemove.toArray(new Annotation[annotationsToRemove.size()]),
new HashMap<Annotation, Position>());
} else {
Iterator<Annotation> annotationIterator = annotationsToRemove.iterator();
while (annotationIterator.hasNext()) {
annotationModel.removeAnnotation(annotationIterator.next());
}
}
cache.put(getOccurrenceAnnotationsCacheKey(), null);
}
//end remove the annotations
} finally {
thread.setPriority(initiaThreadlPriority);
}
}
}