/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation * Anton Leherbauer (Wind River Systems) - Adapted for CDT * Markus Schorn (Wind River Systems) * Sergey Prigogin (Google) *******************************************************************************/ package org.eclipse.cdt.internal.ui.editor; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension4; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IPartListener2; import org.eclipse.ui.IWindowListener; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchPartReference; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.index.IIndex; import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.ILanguage; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.internal.core.model.ASTCache; import org.eclipse.cdt.internal.core.model.ASTCache.ASTRunnable; /** * Provides a shared AST for clients. The shared AST is * the AST of the active CEditor's input element. * * @since 4.0 */ public final class ASTProvider { /** * Wait flag. */ public static final class WAIT_FLAG { String fName; private WAIT_FLAG(String name) { fName= name; } /* * @see java.lang.Object#toString() */ @Override public String toString() { return fName; } } /** * Wait flag indicating that a client requesting an AST * wants to wait until an AST is ready. If the translation unit is not open no AST will * be provided. * <p> * If not yet cached and if the translation unit is open, an AST will be created by * this AST provider. * </p> */ public static final WAIT_FLAG WAIT_IF_OPEN= new WAIT_FLAG("wait if open"); //$NON-NLS-1$ /** * Wait flag indicating that a client requesting an AST * only wants to wait for the shared AST of the active editor. * If the translation unit is not open no AST will be provided. * <p> * No AST will be created by the AST provider. * </p> */ public static final WAIT_FLAG WAIT_ACTIVE_ONLY= new WAIT_FLAG("wait active only"); //$NON-NLS-1$ /** * Wait flag indicating that a client requesting an AST * only wants the already available shared AST. * <p> * No AST will be created by the AST provider. * </p> */ public static final WAIT_FLAG WAIT_NO= new WAIT_FLAG("don't wait"); //$NON-NLS-1$ /** Full parse mode (no PDOM) */ public static int PARSE_MODE_FULL= 0; /** Fast parse mode (use PDOM) */ public static int PARSE_MODE_FAST= ITranslationUnit.AST_SKIP_INDEXED_HEADERS; /** * Internal activation listener. */ private class ActivationListener implements IPartListener2, IWindowListener { /* * @see org.eclipse.ui.IPartListener2#partActivated(org.eclipse.ui.IWorkbenchPartReference) */ public void partActivated(IWorkbenchPartReference ref) { if (isCEditor(ref) && !isActiveEditor(ref)) activeEditorChanged(ref.getPart(true)); } /* * @see org.eclipse.ui.IPartListener2#partBroughtToTop(org.eclipse.ui.IWorkbenchPartReference) */ public void partBroughtToTop(IWorkbenchPartReference ref) { if (isCEditor(ref) && !isActiveEditor(ref)) activeEditorChanged(ref.getPart(true)); } /* * @see org.eclipse.ui.IPartListener2#partClosed(org.eclipse.ui.IWorkbenchPartReference) */ public void partClosed(IWorkbenchPartReference ref) { if (isActiveEditor(ref)) { activeEditorChanged(null); } } /* * @see org.eclipse.ui.IPartListener2#partDeactivated(org.eclipse.ui.IWorkbenchPartReference) */ public void partDeactivated(IWorkbenchPartReference ref) { } /* * @see org.eclipse.ui.IPartListener2#partOpened(org.eclipse.ui.IWorkbenchPartReference) */ public void partOpened(IWorkbenchPartReference ref) { if (isCEditor(ref) && !isActiveEditor(ref)) activeEditorChanged(ref.getPart(true)); } /* * @see org.eclipse.ui.IPartListener2#partHidden(org.eclipse.ui.IWorkbenchPartReference) */ public void partHidden(IWorkbenchPartReference ref) { } /* * @see org.eclipse.ui.IPartListener2#partVisible(org.eclipse.ui.IWorkbenchPartReference) */ public void partVisible(IWorkbenchPartReference ref) { if (isCEditor(ref) && !isActiveEditor(ref)) activeEditorChanged(ref.getPart(true)); } /* * @see org.eclipse.ui.IPartListener2#partInputChanged(org.eclipse.ui.IWorkbenchPartReference) */ public void partInputChanged(IWorkbenchPartReference ref) { if (isCEditor(ref) && isActiveEditor(ref)) activeEditorChanged(ref.getPart(true)); } /* * @see org.eclipse.ui.IWindowListener#windowActivated(org.eclipse.ui.IWorkbenchWindow) */ public void windowActivated(IWorkbenchWindow window) { IWorkbenchPartReference ref= window.getPartService().getActivePartReference(); if (isCEditor(ref) && !isActiveEditor(ref)) activeEditorChanged(ref.getPart(true)); } /* * @see org.eclipse.ui.IWindowListener#windowDeactivated(org.eclipse.ui.IWorkbenchWindow) */ public void windowDeactivated(IWorkbenchWindow window) { } /* * @see org.eclipse.ui.IWindowListener#windowClosed(org.eclipse.ui.IWorkbenchWindow) */ public void windowClosed(IWorkbenchWindow window) { if (fActiveEditor != null && fActiveEditor.getSite() != null && window == fActiveEditor.getSite().getWorkbenchWindow()) { activeEditorChanged(null); } window.getPartService().removePartListener(this); } /* * @see org.eclipse.ui.IWindowListener#windowOpened(org.eclipse.ui.IWorkbenchWindow) */ public void windowOpened(IWorkbenchWindow window) { window.getPartService().addPartListener(this); } private boolean isActiveEditor(IWorkbenchPartReference ref) { return ref != null && isActiveEditor(ref.getPart(false)); } private boolean isActiveEditor(IWorkbenchPart part) { return part != null && part == fActiveEditor; } private boolean isCEditor(IWorkbenchPartReference ref) { if (ref == null) return false; String id= ref.getId(); return CUIPlugin.EDITOR_ID.equals(id) || ref.getPart(false) instanceof CEditor; } } private ASTCache fCache= new ASTCache(); private ActivationListener fActivationListener; private IWorkbenchPart fActiveEditor; private long fTimeStamp; /** * Returns the C plug-in's AST provider. * * @return the AST provider */ public static ASTProvider getASTProvider() { return CUIPlugin.getDefault().getASTProvider(); } /** * Creates a new AST provider. */ public ASTProvider() { install(); } /** * Installs this AST provider. */ void install() { if (PlatformUI.isWorkbenchRunning()) { // Create and register activation listener fActivationListener= new ActivationListener(); PlatformUI.getWorkbench().addWindowListener(fActivationListener); // Ensure existing windows get connected IWorkbenchWindow[] windows= PlatformUI.getWorkbench().getWorkbenchWindows(); for (int i= 0, length= windows.length; i < length; i++) windows[i].getPartService().addPartListener(fActivationListener); } } private void activeEditorChanged(IWorkbenchPart editor) { ICElement cElement= null; if (editor instanceof CEditor) { cElement= ((CEditor) editor).getInputCElement(); } synchronized (this) { fActiveEditor= editor; fTimeStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; fCache.setActiveElement((ITranslationUnit) cElement); } } /** * Informs that reconciling for the given element is about to be started. * * @param cElement the C element * @see org.eclipse.cdt.internal.ui.text.ICReconcilingListener#aboutToBeReconciled() */ void aboutToBeReconciled(ICElement cElement) { if (cElement == null) return; Assert.isTrue(cElement instanceof ITranslationUnit); fCache.aboutToBeReconciled((ITranslationUnit) cElement); updateModificationStamp(); } private boolean updateModificationStamp() { long timeStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; ITextEditor textEditor= null; synchronized (this) { if (fActiveEditor instanceof ITextEditor) { textEditor= (ITextEditor) fActiveEditor; timeStamp= fTimeStamp; } } if (textEditor != null) { IEditorInput editorInput= textEditor.getEditorInput(); IDocument document= textEditor.getDocumentProvider().getDocument(editorInput); if (document instanceof IDocumentExtension4) { IDocumentExtension4 docExt= (IDocumentExtension4) document; long newTimeStamp= docExt.getModificationStamp(); if (newTimeStamp != timeStamp) { synchronized (this) { if (fActiveEditor == textEditor && fTimeStamp == timeStamp) { fTimeStamp= newTimeStamp; return true; } } } } } return false; } /** * Disposes this AST provider. */ public void dispose() { if (fActivationListener != null) { // Dispose activation listener PlatformUI.getWorkbench().removeWindowListener(fActivationListener); fActivationListener= null; } fCache.setActiveElement(null); } /* * @see org.eclipse.cdt.internal.ui.text.ICReconcilingListener#reconciled() */ void reconciled(IASTTranslationUnit ast, ICElement cElement, IProgressMonitor progressMonitor) { if (cElement == null) return; Assert.isTrue(cElement instanceof ITranslationUnit); fCache.reconciled(ast, (ITranslationUnit) cElement); } /** * Executes {@link ASTRunnable#runOnAST(ILanguage, IASTTranslationUnit)} * with the shared AST for the given translation unit. Handles acquiring * and releasing the index read-lock for the client. * * @param cElement the translation unit * @param waitFlag condition for waiting for the AST to be built. * @param monitor a progress monitor, may be <code>null</code> * @param astRunnable the runnable taking the AST * @return the status returned by the ASTRunnable */ public IStatus runOnAST(ICElement cElement, WAIT_FLAG waitFlag, IProgressMonitor monitor, ASTCache.ASTRunnable astRunnable) { Assert.isTrue(cElement instanceof ITranslationUnit); final ITranslationUnit tu = (ITranslationUnit) cElement; if (!prepareForUsingCache(tu, waitFlag)) return Status.CANCEL_STATUS; return fCache.runOnAST(tu, waitFlag != WAIT_NO, monitor, astRunnable); } /** * Returns a shared AST and locks it for exclusive access. An AST obtained from this * method has to be released by calling {@link #releaseSharedAST(IASTTranslationUnit)}. * Subsequent call to this method will block until the AST is released. * <p> * The AST can be released by a thread other than the one that acquired it. * <p> * An index lock must be held by the caller when calling this method. The index lock may * not be released until the AST is released. * * @param tu The translation unit to get the AST for. * @param index index with read lock held. * @param waitFlag condition for waiting for the AST to be built. * @param monitor a progress monitor, may be <code>null</code>. * @return the shared AST, or <code>null</code> if the shared AST is not available. */ public final IASTTranslationUnit acquireSharedAST(ITranslationUnit tu, IIndex index, WAIT_FLAG waitFlag, IProgressMonitor monitor) { if (!prepareForUsingCache(tu, waitFlag)) return null; return fCache.acquireSharedAST(tu, index, waitFlag != WAIT_NO, monitor); } /** * Releases a shared AST previously acquired by calling * {@link #acquireSharedAST(ITranslationUnit, IIndex, WAIT_FLAG, IProgressMonitor)}. * <p> * Can be called by a thread other than the one that acquired the AST. * * @param ast the AST to release. */ public final void releaseSharedAST(IASTTranslationUnit ast) { fCache.releaseSharedAST(ast); } /** * Prepares the AST cache to be used for the given translation unit. * * @param tu the translation unit. * @param waitFlag condition for waiting for the AST to be built. * @return <code>true</code> if the AST cache can be used for the given translation unit, * <code>false</code> otherwise. */ private boolean prepareForUsingCache(ITranslationUnit tu, WAIT_FLAG waitFlag) { if (!tu.isOpen()) return false; // http://bugs.eclipse.org/bugs/show_bug.cgi?id=342506 explains // benign nature of the race conditions in the code below. final boolean isActive= fCache.isActiveElement(tu); if (waitFlag == WAIT_ACTIVE_ONLY && !isActive) { return false; } if (isActive && updateModificationStamp()) { fCache.disposeAST(); } return true; } }