/** * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ /* * Created on Apr 29, 2006 */ package com.python.pydev.refactoring.markoccurrences; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.AssertionFailedException; 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.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.IAction; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; 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.core.MisconfigurationException; import org.python.pydev.core.Tuple3; import org.python.pydev.core.docutils.PySelection; import org.python.pydev.core.log.Log; import org.python.pydev.editor.PyEdit; import org.python.pydev.editor.actions.refactoring.PyRefactorAction; import org.python.pydev.editor.codefolding.PySourceViewer; import org.python.pydev.editor.refactoring.RefactoringRequest; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.Name; import org.python.pydev.parser.visitors.scope.ASTEntry; import com.python.pydev.PydevPlugin; import com.python.pydev.refactoring.refactorer.AstEntryRefactorerRequestConstants; import com.python.pydev.refactoring.ui.MarkOccurrencesPreferencesPage; import com.python.pydev.refactoring.wizards.rename.PyRenameEntryPoint; /** * 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 class MarkOccurrencesJob extends Job { private static final boolean DEBUG = false; private static MarkOccurrencesJob singleton; /** * Make it thread safe */ private static volatile long lastRequestTime = -1; /** * This is the editor to be analyzed */ private WeakReference<PyEdit> editor; /** * This is the request time for this job */ private long currRequestTime = -1; /** * The selection when the occurrences job was requested */ private PySelection ps; private MarkOccurrencesJob(WeakReference<PyEdit> editor, PySelection ps) { super("MarkOccurrencesJob"); setPriority(Job.BUILD); setSystem(true); this.editor = editor; this.ps = ps; currRequestTime = System.currentTimeMillis(); } /** * Mark if we are still abel to do it by the time we get to the run. */ public IStatus run(IProgressMonitor monitor) { if (currRequestTime == -1) { return Status.OK_STATUS; } if (currRequestTime == lastRequestTime) { return Status.OK_STATUS; } lastRequestTime = currRequestTime; try { final PyEdit pyEdit = editor.get(); if (pyEdit == null || monitor.isCanceled()) { return Status.OK_STATUS; } try { IDocumentProvider documentProvider = pyEdit.getDocumentProvider(); if (documentProvider == null || monitor.isCanceled()) { return Status.OK_STATUS; } IAnnotationModel annotationModel = documentProvider.getAnnotationModel(pyEdit.getEditorInput()); if (annotationModel == null || monitor.isCanceled()) { return Status.OK_STATUS; } Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean> ret = checkAnnotations(pyEdit, documentProvider, monitor); if (pyEdit.cache == null || monitor.isCanceled()) { //disposed (cannot add or remove annotations) return Status.OK_STATUS; } PySourceViewer viewer = pyEdit.getPySourceViewer(); if (viewer == null || monitor.isCanceled()) { return Status.OK_STATUS; } if (viewer.getIsInToggleCompletionStyle() || monitor.isCanceled()) { return Status.OK_STATUS; } if (ret.o3) { if (!addAnnotations(pyEdit, annotationModel, ret.o1, ret.o2)) { //something went wrong, so, let's remove the occurrences removeOccurenceAnnotations(annotationModel, pyEdit); } } else { removeOccurenceAnnotations(annotationModel, pyEdit); } } 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:" + pyEdit.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; } /** * @return a tuple with the refactoring request, the processor and a boolean indicating if all pre-conditions succedded. * @throws MisconfigurationException */ private Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean> checkAnnotations(PyEdit pyEdit, IDocumentProvider documentProvider, IProgressMonitor monitor) throws BadLocationException, OperationCanceledException, CoreException, MisconfigurationException { if (!MarkOccurrencesPreferencesPage.useMarkOccurrences()) { return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false); } //now, let's see if the editor still has a document (so that we still can add stuff to it) IEditorInput editorInput = pyEdit.getEditorInput(); if (editorInput == null) { return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false); } if (documentProvider.getDocument(editorInput) == null) { return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false); } if (pyEdit.getSelectionProvider() == null) { return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false); } //ok, the editor is still there wit ha document... move on PyRefactorAction pyRefactorAction = getRefactorAction(pyEdit); final RefactoringRequest req = getRefactoringRequest(pyEdit, pyRefactorAction, this.ps); if (req == null || !req.nature.getRelatedInterpreterManager().isConfigured()) { //we check if it's configured because it may still be a stub... return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false); } PyRenameEntryPoint processor = new PyRenameEntryPoint(req); //to see if a new request was not created in the meantime (in which case this one will be cancelled) if (currRequestTime != lastRequestTime || monitor.isCanceled()) { return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false); } try { processor.checkInitialConditions(monitor); if (currRequestTime != lastRequestTime || monitor.isCanceled()) { return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false); } processor.checkFinalConditions(monitor, null); if (currRequestTime != lastRequestTime || monitor.isCanceled()) { return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false); } //ok, pre-conditions suceeded return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(req, processor, true); } catch (Throwable e) { throw new RuntimeException("Error in occurrences while analyzing modName:" + req.moduleName + " initialName:" + req.initialName + " line (start at 0):" + req.ps.getCursorLine(), e); } } /** * @return true if the annotations were removed and added without any problems and false otherwise */ private synchronized boolean addAnnotations(final PyEdit pyEdit, IAnnotationModel annotationModel, final RefactoringRequest req, PyRenameEntryPoint processor) throws BadLocationException { HashSet<ASTEntry> occurrences = processor.getOccurrences(); if (occurrences == null) { if (DEBUG) { System.out.println("Occurrences == null"); } return false; } Map<String, Object> cache = pyEdit.cache; if (cache == null) { return false; } IDocument doc = pyEdit.getDocument(); ArrayList<Annotation> annotations = new ArrayList<Annotation>(); Map<Annotation, Position> toAddAsMap = new HashMap<Annotation, Position>(); boolean markOccurrencesInStrings = MarkOccurrencesPreferencesPage.useMarkOccurrencesInStrings(); //get the annotations to add for (ASTEntry entry : occurrences) { if (!markOccurrencesInStrings) { if (entry.node instanceof Name) { Name name = (Name) entry.node; if (name.ctx == Name.Artificial) { continue; } } } SimpleNode node = entry.getNameNode(); IRegion lineInformation = doc.getLineInformation(node.beginLine - 1); try { Annotation annotation = new Annotation(PydevPlugin.OCCURRENCE_ANNOTATION_TYPE, false, "occurrence"); Position position = new Position(lineInformation.getOffset() + node.beginColumn - 1, req.initialName.length()); toAddAsMap.put(annotation, position); annotations.add(annotation); } catch (Exception e) { Log.log(e); } } //get the ones to remove List<Annotation> toRemove = PydevPlugin.getOccurrenceAnnotationsInPyEdit(pyEdit); //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]), toAddAsMap); } } finally { thread.setPriority(initiaThreadlPriority); } //put them in the pyEdit cache.put(PydevPlugin.ANNOTATIONS_CACHE_KEY, annotations); return true; } /** * @param pyEdit the editor where we should look for the occurrences * @param pyRefactorAction the action that will return the initial refactoring request * @param ps the pyselection used (if null it will be created in this method) * @return a refactoring request suitable for finding the locals in the file * @throws BadLocationException * @throws MisconfigurationException */ public static RefactoringRequest getRefactoringRequest(final PyEdit pyEdit, PyRefactorAction pyRefactorAction, PySelection ps) throws BadLocationException, MisconfigurationException { final RefactoringRequest req = pyRefactorAction.getRefactoringRequest(); req.ps = ps; req.fillInitialNameAndOffset(); req.inputName = "foo"; req.setAdditionalInfo(AstEntryRefactorerRequestConstants.FIND_DEFINITION_IN_ADDITIONAL_INFO, false); req.setAdditionalInfo(AstEntryRefactorerRequestConstants.FIND_REFERENCES_ONLY_IN_LOCAL_SCOPE, true); return req; } /** * @param pyEdit the editor that will have this action * @return the action (with the pyedit attached to it) */ public static PyRefactorAction getRefactorAction(PyEdit pyEdit) { PyRefactorAction pyRefactorAction = new PyRefactorAction() { @Override protected String perform(IAction action, IProgressMonitor monitor) throws Exception { throw new RuntimeException("Perform should not be called in this case."); } }; pyRefactorAction.setEditor(pyEdit); return pyRefactorAction; } /** * @param annotationModel */ private synchronized void removeOccurenceAnnotations(IAnnotationModel annotationModel, PyEdit 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 = PydevPlugin.getOccurrenceAnnotationsInPyEdit(pyEdit); if (annotationModel instanceof IAnnotationModelExtension) { //replace those ((IAnnotationModelExtension) annotationModel).replaceAnnotations( annotationsToRemove.toArray(new Annotation[annotationsToRemove.size()]), new HashMap()); } else { Iterator<Annotation> annotationIterator = annotationsToRemove.iterator(); while (annotationIterator.hasNext()) { annotationModel.removeAnnotation(annotationIterator.next()); } } cache.put(PydevPlugin.ANNOTATIONS_CACHE_KEY, null); } //end remove the annotations } finally { thread.setPriority(initiaThreadlPriority); } } /** * Gotten from JavaEditor#getLockObject */ private Object getLockObject(IAnnotationModel annotationModel) { if (annotationModel instanceof ISynchronizable) return ((ISynchronizable) annotationModel).getLockObject(); else return annotationModel; } /** * 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(WeakReference<PyEdit> editor2, PySelection ps) { MarkOccurrencesJob j = singleton; if (j != null) { synchronized (j) { j.cancel(); singleton = null; } } singleton = new MarkOccurrencesJob(editor2, ps); singleton.schedule(750); } }