/** * 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. */ package com.python.pydev.analysis.builder; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.python.pydev.core.IPythonNature; import org.python.pydev.core.log.Log; import org.python.pydev.logging.DebugSettings; /** * Abstract class for the builder runnables. * * @author Fabio */ public abstract class AbstractAnalysisBuilderRunnable implements IAnalysisBuilderRunnable { // -------------------------------------------------------------------------------------------- ATTRIBUTES protected IProgressMonitor monitorSetExternally; //from IRunnableWithMonitor public void setMonitor(IProgressMonitor monitor) { monitorSetExternally = monitor; } final protected IProgressMonitor internalCancelMonitor = new NullProgressMonitor() { public final boolean isCanceled() { if (super.isCanceled()) { return true; } IProgressMonitor ext = AbstractAnalysisBuilderRunnable.this.monitorSetExternally; if (ext != null && ext.isCanceled()) { return true; } return false; }; }; final protected String moduleName; final protected boolean isFullBuild; final protected boolean forceAnalysis; final protected int analysisCause; final protected KeyForAnalysisRunnable key; final private Object lock = new Object(); protected IPythonNature nature; protected volatile boolean runFinished = false; private IAnalysisBuilderRunnable oldAnalysisBuilderThread; private long documentTime; private long resourceModificationStamp; // ---------------------------------------------------------------------------------------- END ATTRIBUTES public AbstractAnalysisBuilderRunnable(boolean isFullBuild, String moduleName, boolean forceAnalysis, int analysisCause, IAnalysisBuilderRunnable oldAnalysisBuilderThread, IPythonNature nature, long documentTime, KeyForAnalysisRunnable key, long resourceModificationStamp) { this.isFullBuild = isFullBuild; this.moduleName = moduleName; this.forceAnalysis = forceAnalysis; this.analysisCause = analysisCause; this.oldAnalysisBuilderThread = oldAnalysisBuilderThread; this.nature = nature; this.documentTime = documentTime; this.key = key; this.resourceModificationStamp = resourceModificationStamp; } public long getDocumentTime() { return documentTime; } public long getResourceModificationStamp() { return resourceModificationStamp; } public int getAnalysisCause() { return analysisCause; } public boolean getForceAnalysis() { return forceAnalysis; } public synchronized boolean getRunFinished() { return runFinished; } public String getModuleName() { return moduleName; } public String getAnalysisCauseStr() { String analysisCauseStr; if (analysisCause == ANALYSIS_CAUSE_BUILDER) { analysisCauseStr = "Builder"; } else if (analysisCause == ANALYSIS_CAUSE_PARSER) { analysisCauseStr = "Parser"; } else { analysisCauseStr = "Unknown?"; } return analysisCauseStr; } protected void logOperationCancelled() { if (DebugSettings.DEBUG_ANALYSIS_REQUESTS) { Log.toLogFile(this, "OperationCanceledException: cancelled by new runnable -- " + moduleName + ". Cancelled was from: " + getAnalysisCauseStr()); } } /** * The run for this runnable will only start if there's no other runnable active for the same module. * * This method will do that and call doAnalysis() if it hasn't been cancelled itself. */ public void run() { try { try { if (oldAnalysisBuilderThread != null) { if (DebugSettings.DEBUG_ANALYSIS_REQUESTS) { Log.toLogFile(this, "Waiting for other to be finished..."); } //just to make sure that the analysis of the existing runnable had a request for stopping already oldAnalysisBuilderThread.stopAnalysis(); int attempts = 0; while (!oldAnalysisBuilderThread.getRunFinished()) { attempts += 1; synchronized (lock) { try { Thread.sleep(50); } catch (InterruptedException e) { //ignore } } } if (DebugSettings.DEBUG_ANALYSIS_REQUESTS) { Log.toLogFile(this, "Starting analysis after attempts: " + attempts); } } //that's all we need it for... we can already dispose of it. this.oldAnalysisBuilderThread = null; if (!internalCancelMonitor.isCanceled()) { doAnalysis(); } else { logOperationCancelled(); } } catch (NoClassDefFoundError e) { //ignore, plugin finished and thread still active } } catch (Exception e) { Log.log(e); } finally { try { AnalysisBuilderRunnableFactory.removeFromThreads(key, this); } catch (Throwable e) { Log.log(e); } finally { runFinished = true; } dispose(); } } protected void dispose() { this.nature = null; this.oldAnalysisBuilderThread = null; } /** * This method should be overridden to actually make the action that this analysis triggered. */ protected abstract void doAnalysis(); /** * Stops the analysis whenever it gets a chance to do so. */ public synchronized void stopAnalysis() { this.internalCancelMonitor.setCanceled(true); } private final static OperationCanceledException operationCanceledException = new OperationCanceledException(); /** * Checks if the analysis in this runnable should be stopped (raises an OperationCanceledException if it should be stopped) */ protected void checkStop() { if (this.internalCancelMonitor.isCanceled()) { throw operationCanceledException; } } }