/******************************************************************************* * Copyright (c) 2008 IBM Corporation. * All rights reserved. This program and the accompanying materials * are 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: * Robert Fuhrer (rfuhrer@watson.ibm.com) - initial API and implementation *******************************************************************************/ package org.eclipse.imp.editor; import java.util.ArrayList; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.imp.core.ErrorHandler; import org.eclipse.imp.language.Language; import org.eclipse.imp.language.LanguageRegistry; import org.eclipse.imp.model.ISourceProject; import org.eclipse.imp.parser.IMessageHandler; import org.eclipse.imp.parser.IMessageHandlerExtension; import org.eclipse.imp.parser.IModelListener; import org.eclipse.imp.parser.IParseController; import org.eclipse.imp.preferences.PreferenceCache; import org.eclipse.imp.runtime.RuntimePlugin; import org.eclipse.jface.text.IDocument; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.texteditor.IDocumentProvider; /** * Parsing may take a long time, and is not done inside the UI thread. Therefore, we create a job that is executed in a * background thread by the platform's job service. */ // TODO Perhaps this should be driven off of the "IReconcilingStrategy" mechanism? public class ParserScheduler extends Job { private final IParseController fParseController; private final IEditorPart fEditorPart; private final IDocumentProvider fDocumentProvider; private final IMessageHandler fMsgHandler; private final List<IModelListener> fAstListeners= new ArrayList<IModelListener>(); // private final IPreferencesService fPrefService; public ParserScheduler(IParseController parseController, IEditorPart editorPart, IDocumentProvider docProvider, IMessageHandler msgHandler) { super(LanguageRegistry.findLanguage(EditorInputUtils.getPath(editorPart.getEditorInput()), null).getName() + " ParserScheduler for " + editorPart.getEditorInput().getName()); setSystem(true); // do not show this job in the Progress view fParseController= parseController; fEditorPart= editorPart; fDocumentProvider= docProvider; fMsgHandler= msgHandler; // fPrefService= new PreferencesService(fParseController.getProject().getRawProject(), fParseController.getLanguage().getName()); // rmf 7/1/2008 - N.B. The parse controller is now initialized before it gets handed to us here, // since some other services may actually depend on that. } public IStatus run(IProgressMonitor monitor) { if (fParseController == null || fDocumentProvider == null) { /* Editor was closed, or no parse controller */ return Status.OK_STATUS; } IEditorInput editorInput= fEditorPart.getEditorInput(); try { IDocument document= fDocumentProvider.getDocument(editorInput); if (document == null) return Status.OK_STATUS; if (PreferenceCache.emitMessages /* fPrefService.getBooleanPreference(PreferenceConstants.P_EMIT_MESSAGES) */) { RuntimePlugin.getInstance().writeInfoMsg( "Parsing language " + fParseController.getLanguage().getName() + " for input " + editorInput.getName()); } // System.out.println("Parsing started."); // If we're editing a workspace resource, check to make sure that it still exists if (sourceStillExists()) { fMsgHandler.clearMessages(); // Don't bother to retrieve the AST; we don't need it; just make sure the document gets parsed. fParseController.parse(document.get(), monitor); if (fMsgHandler instanceof IMessageHandlerExtension) { ((IMessageHandlerExtension) fMsgHandler).endMessages(); } // } else { // System.err.println("Scheduled parsing was bypassed due to project deletion."); } // System.out.println("Parsing complete."); if (!monitor.isCanceled()) { notifyModelListeners(monitor); } } catch (Exception e) { Language lang = fParseController.getLanguage(); String input = editorInput != null ? editorInput.getName() : "<unknown editor input"; String name = lang != null ? lang.getName() : "<unknown language>"; ErrorHandler.reportError("Error running parser for language " + name + " and input " + input + ":", e); // RMF 8/2/2006 - Notify the AST listeners even on an exception - the compiler front end // may have failed at some phase, but there may be enough info to drive IDE services. notifyModelListeners(monitor); } catch (LinkageError e) { // Catch things like NoClassDefFoundError that might result from, e.g., errors in plugin metadata, classpath, etc. ErrorHandler.reportError("Error loading IParseController implementation class for language " + fParseController.getLanguage().getName(), e); } return Status.OK_STATUS; } private boolean sourceStillExists() { ISourceProject project= fParseController.getProject(); if (project == null) { return true; // this wasn't a workspace resource to begin with } IProject rawProject= project.getRawProject(); if (!rawProject.exists()) { return false; } IFile file= rawProject.getFile(fParseController.getPath()); return file.exists(); } public void addModelListener(IModelListener listener) { fAstListeners.add(listener); } public void removeModelListener(IModelListener listener) { fAstListeners.remove(listener); } public void notifyModelListeners(IProgressMonitor monitor) { // Suppress the notification if there's no AST (e.g. due to a parse error) if (fParseController != null) { if ( PreferenceCache.emitMessages // TODO RMF Switch to pref svc for this, once the "global" IMP preferences (using the "IMP" pseudo-language name) gets initialized properly // fPrefService.isDefined(PreferenceConstants.P_EMIT_MESSAGES) && // fPrefService.getBooleanPreference(PreferenceConstants.P_EMIT_MESSAGES) || // RuntimePlugin.getInstance().getPreferencesService().getBooleanPreference(PreferenceConstants.P_EMIT_MESSAGES) ) { RuntimePlugin.getInstance().writeInfoMsg( "Notifying AST listeners of change in " + (fParseController.getPath() != null ? fParseController.getPath().toPortableString() : "<unknown file>")); } for(int n= fAstListeners.size() - 1; n >= 0 && !monitor.isCanceled(); n--) { IModelListener listener= fAstListeners.get(n); // Pretend to get through the highest level of analysis so all services execute (for now) int analysisLevel= IModelListener.AnalysisRequired.POINTER_ANALYSIS.level(); if (fParseController.getCurrentAst() == null) analysisLevel= IModelListener.AnalysisRequired.LEXICAL_ANALYSIS.level(); // TODO How to tell how far we got with the source analysis? The IAnalysisController should tell us! // TODO Rename IParseController to IAnalysisController // TODO Compute the minimum amount of analysis sufficient for all current listeners, and pass that to // the IAnalysisController. if (listener.getAnalysisRequired().level() <= analysisLevel) { listener.update(fParseController, monitor); } } // long curTime= System.currentTimeMillis(); // System.out.println("All model listeners notified; time = " + curTime); // long diffToRuntimeStart= curTime - RuntimePlugin.PRE_STARTUP_TIME; // long diffToEditorStart= curTime - RuntimePlugin.EDITOR_START_TIME; // System.out.println("Time from runtime start: " + diffToRuntimeStart); // System.out.println("Time from editor start: " + diffToEditorStart); } else if (PreferenceCache.emitMessages // fPrefService.isDefined(PreferenceConstants.P_EMIT_MESSAGES) && // fPrefService.getBooleanPreference(PreferenceConstants.P_EMIT_MESSAGES) || // RuntimePlugin.getInstance().getPreferencesService().getBooleanPreference(PreferenceConstants.P_EMIT_MESSAGES) ) { RuntimePlugin.getInstance().writeInfoMsg("No AST; bypassing listener notification."); } } }