/**
* 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 6, 2006
*/
package com.python.pydev.analysis.builder;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jface.text.IDocument;
import org.python.pydev.builder.PyDevBuilderPrefPage;
import org.python.pydev.builder.PyDevBuilderVisitor;
import org.python.pydev.core.IModule;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.PyEdit;
import org.python.pydev.editor.autoedit.DefaultIndentPrefs;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceModule;
import org.python.pydev.logging.DebugSettings;
import com.aptana.shared_core.callbacks.ICallback;
import com.python.pydev.analysis.AnalysisPreferences;
import com.python.pydev.analysis.IAnalysisPreferences;
import com.python.pydev.analysis.OccurrencesAnalyzer;
import com.python.pydev.analysis.additionalinfo.AbstractAdditionalTokensInfo;
import com.python.pydev.analysis.additionalinfo.AdditionalProjectInterpreterInfo;
import com.python.pydev.analysis.messages.IMessage;
/**
* This class is used to do analysis on a thread, so that if an analysis is asked for some analysis that
* is already in progress, that analysis will be stopped and this one will begin.
*
* @author Fabio
*/
public class AnalysisBuilderRunnable extends AbstractAnalysisBuilderRunnable {
/**
* These are the callbacks called whenever there's a run to be done in this class.
*/
public static final List<ICallback<Object, IResource>> analysisBuilderListeners = new ArrayList<ICallback<Object, IResource>>();
// -------------------------------------------------------------------------------------------- ATTRIBUTES
private IDocument document;
private IResource resource;
private ICallback<IModule, Integer> module;
// ---------------------------------------------------------------------------------------- END ATTRIBUTES
/**
* Versions before eclipse 3.4 don't have an isDerived(IResource.CHECK_ANCESTORS)
*/
private static boolean useEclipse32DerivedVersion = false;
/**
* Checks if some resource is hierarchically derived (if any parent is derived, it's also derived).
*/
private static boolean isHierarchicallyDerived(IResource curr) {
if (useEclipse32DerivedVersion) {
do {
if (curr.isDerived()) {
return true;
}
curr = curr.getParent();
} while (curr != null);
return false;
} else {
try {
return curr.isDerived(IResource.CHECK_ANCESTORS);
} catch (Throwable e) {
useEclipse32DerivedVersion = true;
return isHierarchicallyDerived(curr);
}
}
}
/**
* @param oldAnalysisBuilderThread This is an existing runnable that was already analyzing things... we must wait for it
* to finish to start it again.
*
* @param module: this is a callback that'll be called with a boolean that should return the IModule to be used in the
* analysis.
* The parameter is FULL_MODULE or DEFINITIONS_MODULE
*/
/*Default*/AnalysisBuilderRunnable(IDocument document, IResource resource, ICallback<IModule, Integer> module,
boolean isFullBuild, String moduleName, boolean forceAnalysis, int analysisCause,
IAnalysisBuilderRunnable oldAnalysisBuilderThread, IPythonNature nature, long documentTime,
KeyForAnalysisRunnable key, long resourceModificationStamp) {
super(isFullBuild, moduleName, forceAnalysis, analysisCause, oldAnalysisBuilderThread, nature, documentTime,
key, resourceModificationStamp);
if (resource == null) {
Log.toLogFile(this, "Unexpected null resource for: " + moduleName);
return;
}
this.document = document;
this.resource = resource;
this.module = module;
}
protected void dispose() {
super.dispose();
this.document = null;
this.resource = null;
this.module = null;
}
protected void doAnalysis() {
if (!nature.startRequests()) {
return;
}
try {
if (DebugSettings.DEBUG_ANALYSIS_REQUESTS) {
Log.toLogFile(this, "doAnalysis() - " + moduleName + " " + this.getAnalysisCauseStr());
}
//if the resource is not open, there's not much we can do...
final IResource r = resource;
if (r == null) {
Log.toLogFile(this, "Finished analysis -- resource null -- " + moduleName);
return;
}
if (!r.getProject().isOpen()) {
Log.toLogFile(this, "Finished analysis -- project closed -- " + moduleName);
return;
}
AnalysisRunner runner = new AnalysisRunner();
checkStop();
IAnalysisPreferences analysisPreferences = AnalysisPreferences.getAnalysisPreferences();
//update the severities, etc.
analysisPreferences.clearCaches();
boolean makeAnalysis = runner.canDoAnalysis(document) && PyDevBuilderVisitor.isInPythonPath(r) && //just get problems in resources that are in the pythonpath
analysisPreferences.makeCodeAnalysis();
if (!makeAnalysis) {
//let's see if we should do code analysis
AnalysisRunner.deleteMarkers(r);
}
if (nature == null) {
Log.log("Finished analysis: null nature -- " + moduleName);
return;
}
AbstractAdditionalTokensInfo info = AdditionalProjectInterpreterInfo.getAdditionalInfoForProject(nature);
if (info == null) {
Log.log("Unable to get additional info for: " + r + " -- " + moduleName);
return;
}
checkStop();
//remove dependency information (and anything else that was already generated), but first, gather
//the modules dependent on this one.
if (!isFullBuild) {
//if it is a full build, that info is already removed
AnalysisBuilderRunnableForRemove.removeInfoForModule(moduleName, nature, isFullBuild);
}
boolean onlyRecreateCtxInsensitiveInfo = !forceAnalysis && analysisCause == ANALYSIS_CAUSE_BUILDER
&& PyDevBuilderPrefPage.getAnalyzeOnlyActiveEditor();
if (!onlyRecreateCtxInsensitiveInfo) {
//if not a source folder, we'll just want to recreate the context insensitive information
if (!nature.isResourceInPythonpathProjectSources(r, false)) {
onlyRecreateCtxInsensitiveInfo = true;
}
}
int moduleRequest;
if (onlyRecreateCtxInsensitiveInfo) {
moduleRequest = DEFINITIONS_MODULE;
} else {
moduleRequest = FULL_MODULE;
}
//get the module for the analysis
checkStop();
SourceModule module = (SourceModule) this.module.call(moduleRequest);
checkStop();
//recreate the ctx insensitive info
recreateCtxInsensitiveInfo(info, module, nature, r);
if (onlyRecreateCtxInsensitiveInfo) {
if (DebugSettings.DEBUG_ANALYSIS_REQUESTS) {
Log.toLogFile(this, "Skipping: !forceAnalysis && analysisCause == ANALYSIS_CAUSE_BUILDER && "
+ "PyDevBuilderPrefPage.getAnalyzeOnlyActiveEditor() -- " + moduleName);
}
return;
}
//let's see if we should continue with the process
if (!makeAnalysis) {
if (DebugSettings.DEBUG_ANALYSIS_REQUESTS) {
Log.toLogFile(this, "Skipping: !makeAnalysis -- " + moduleName);
}
return;
}
if (DebugSettings.DEBUG_ANALYSIS_REQUESTS) {
Log.toLogFile(this, "makeAnalysis:" + makeAnalysis + " " + "analysisCause: " + getAnalysisCauseStr()
+ " -- " + moduleName);
}
checkStop();
if (isHierarchicallyDerived(r)) {
if (DebugSettings.DEBUG_ANALYSIS_REQUESTS) {
Log.toLogFile(this, "Resource marked as derived not analyzed: " + r + " -- " + moduleName);
}
//We don't want to check derived resources (but we want to remove any analysis messages that
//might be already there)
if (r != null) {
runner.setMarkers(r, document, new IMessage[0], this.internalCancelMonitor);
}
return;
}
//ok, let's do it
OccurrencesAnalyzer analyzer = new OccurrencesAnalyzer();
checkStop();
IMessage[] messages = analyzer.analyzeDocument(nature, module, analysisPreferences, document,
this.internalCancelMonitor, DefaultIndentPrefs.get());
checkStop();
if (DebugSettings.DEBUG_ANALYSIS_REQUESTS) {
Log.toLogFile(this, "Adding markers for module: " + moduleName);
//for (IMessage message : messages) {
// Log.toLogFile(this, message.toString());
//}
}
//last chance to stop...
checkStop();
//don't stop after setting to add / remove the markers
if (r != null) {
boolean analyzeOnlyActiveEditor = PyDevBuilderPrefPage.getAnalyzeOnlyActiveEditor();
if (forceAnalysis
|| !analyzeOnlyActiveEditor
|| (analyzeOnlyActiveEditor && (!PyDevBuilderPrefPage.getRemoveErrorsWhenEditorIsClosed() || PyEdit
.isEditorOpenForResource(r)))) {
runner.setMarkers(r, document, messages, this.internalCancelMonitor);
} else {
if (DebugSettings.DEBUG_ANALYSIS_REQUESTS) {
Log.toLogFile(this, "Skipped adding markers for module: " + moduleName
+ " (editor not opened).");
}
}
}
//if there are callbacks registered, call them if we still didn't return (mostly for tests)
for (ICallback<Object, IResource> callback : analysisBuilderListeners) {
try {
callback.call(r);
} catch (Exception e) {
Log.log(e);
}
}
} catch (OperationCanceledException e) {
//ok, ignore it
logOperationCancelled();
} catch (Exception e) {
Log.log(e);
} finally {
try {
nature.endRequests();
} catch (Throwable e) {
Log.log("Error when analyzing: " + moduleName, e);
}
try {
AnalysisBuilderRunnableFactory.removeFromThreads(key, this);
} catch (Throwable e) {
Log.log(e);
}
dispose();
}
}
/**
* @return false if there's no modification among the current version of the file and the last version analyzed.
*/
private void recreateCtxInsensitiveInfo(AbstractAdditionalTokensInfo info, SourceModule sourceModule,
IPythonNature nature, IResource r) {
//info.removeInfoFromModule(sourceModule.getName()); -- does not remove info from the module because this
//should be already done once it gets here (the AnalysisBuilder, that also makes dependency info
//should take care of this).
boolean generateDelta;
if (isFullBuild) {
generateDelta = false;
} else {
generateDelta = true;
}
info.addAstInfo(sourceModule.getAst(), sourceModule.getModulesKey(), generateDelta);
}
}