/******************************************************************************* * Copyright (c) 2000, 2014 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 *******************************************************************************/ package org.eclipse.wst.jsdt.internal.ui.text; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.reconciler.DirtyRegion; import org.eclipse.jface.text.reconciler.MonoReconciler; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.swt.events.ShellAdapter; import org.eclipse.swt.events.ShellEvent; import org.eclipse.swt.events.ShellListener; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IPartListener; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchPartSite; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.spelling.SpellingService; import org.eclipse.wst.jsdt.core.ElementChangedEvent; import org.eclipse.wst.jsdt.core.IElementChangedListener; import org.eclipse.wst.jsdt.core.IJavaScriptElementDelta; import org.eclipse.wst.jsdt.core.JavaScriptCore; import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin; import org.eclipse.wst.jsdt.internal.ui.javaeditor.CompilationUnitEditor; /** * A reconciler that is also activated on editor activation. */ public class JavaReconciler extends MonoReconciler { /** * Internal part listener for activating the reconciler. */ private class PartListener implements IPartListener { /* * @see org.eclipse.ui.IPartListener#partActivated(org.eclipse.ui.IWorkbenchPart) */ public void partActivated(IWorkbenchPart part) { if (part == fTextEditor) { if (hasJavaModelChanged()) JavaReconciler.this.forceReconciling(); setEditorActive(true); } } /* * @see org.eclipse.ui.IPartListener#partBroughtToTop(org.eclipse.ui.IWorkbenchPart) */ public void partBroughtToTop(IWorkbenchPart part) { } /* * @see org.eclipse.ui.IPartListener#partClosed(org.eclipse.ui.IWorkbenchPart) */ public void partClosed(IWorkbenchPart part) { } /* * @see org.eclipse.ui.IPartListener#partDeactivated(org.eclipse.ui.IWorkbenchPart) */ public void partDeactivated(IWorkbenchPart part) { if (part == fTextEditor) { setJavaModelChanged(false); setEditorActive(false); } } /* * @see org.eclipse.ui.IPartListener#partOpened(org.eclipse.ui.IWorkbenchPart) */ public void partOpened(IWorkbenchPart part) { } } /** * Internal Shell activation listener for activating the reconciler. */ private class ActivationListener extends ShellAdapter { private Control fControl; public ActivationListener(Control control) { Assert.isNotNull(control); fControl= control; } /* * @see org.eclipse.swt.events.ShellListener#shellActivated(org.eclipse.swt.events.ShellEvent) */ public void shellActivated(ShellEvent e) { if (!fControl.isDisposed() && fControl.isVisible()) { if (hasJavaModelChanged()) JavaReconciler.this.forceReconciling(); setEditorActive(true); } } /* * @see org.eclipse.swt.events.ShellListener#shellDeactivated(org.eclipse.swt.events.ShellEvent) */ public void shellDeactivated(ShellEvent e) { if (!fControl.isDisposed() && fControl.getShell() == e.getSource()) { setJavaModelChanged(false); setEditorActive(false); } } } /** * Internal Java element changed listener * * */ private class ElementChangedListener implements IElementChangedListener { /* * @see org.eclipse.wst.jsdt.core.IElementChangedListener#elementChanged(org.eclipse.wst.jsdt.core.ElementChangedEvent) */ public void elementChanged(ElementChangedEvent event) { if (event.getDelta().getFlags() == IJavaScriptElementDelta.F_AST_AFFECTED) return; setJavaModelChanged(true); if (!fIsReconciling && isEditorActive() ) JavaReconciler.this.forceReconciling(); } } /** * Internal resource change listener. * * */ class ResourceChangeListener implements IResourceChangeListener { private IResource getResource() { IEditorInput input= fTextEditor.getEditorInput(); if (input instanceof IFileEditorInput) { IFileEditorInput fileInput= (IFileEditorInput) input; return fileInput.getFile(); } return null; } /* * @see IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) */ public void resourceChanged(IResourceChangeEvent e) { IResourceDelta delta= e.getDelta(); IResource resource= getResource(); if (delta != null && resource != null) { IResourceDelta child= delta.findMember(resource.getFullPath()); if (child != null) { IMarkerDelta[] deltas= child.getMarkerDeltas(); int i= deltas.length; while (--i >= 0) { try { if (deltas[i].getMarker().isSubtypeOf(IMarker.PROBLEM)) { forceReconciling(); return; } } catch (CoreException e1) { // ignore and try next one } } } } } } /** The reconciler's editor */ private ITextEditor fTextEditor; /** The part listener */ private IPartListener fPartListener; /** The shell listener */ private ShellListener fActivationListener; /** * The mutex that keeps us from running multiple reconcilers on one editor. */ private Object fMutex; /** * The Java element changed listener. * */ private IElementChangedListener fJavaElementChangedListener; /** * Tells whether the Java model sent out a changed event. * */ private volatile boolean fHasJavaModelChanged= true; /** * Tells whether this reconciler's editor is active. * */ private volatile boolean fIsEditorActive= true; /** * The resource change listener. * */ private IResourceChangeListener fResourceChangeListener; /** * The property change listener. * */ private IPropertyChangeListener fPropertyChangeListener; /** * Tells whether a reconcile is in progress. * */ private volatile boolean fIsReconciling= false; private boolean fIninitalProcessDone= false; /** * Creates a new reconciler. * * @param editor the editor * @param strategy the reconcile strategy * @param isIncremental <code>true</code> if this is an incremental reconciler */ public JavaReconciler(ITextEditor editor, JavaCompositeReconcilingStrategy strategy, boolean isIncremental) { super(strategy, isIncremental); fTextEditor= editor; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=63898 // when re-using editors, a new reconciler is set up by the source viewer // and the old one uninstalled. However, the old reconciler may still be // running. // To avoid having to reconcilers calling CompilationUnitEditor.reconciled, // we synchronized on a lock object provided by the editor. // The critical section is really the entire run() method of the reconciler // thread, but synchronizing process() only will keep JavaReconcilingStrategy // from running concurrently on the same editor. // TODO remove once we have ensured that there is only one reconciler per editor. if (editor instanceof CompilationUnitEditor) fMutex= ((CompilationUnitEditor) editor).getReconcilerLock(); else fMutex= new Object(); // Null Object } /* * @see org.eclipse.jface.text.reconciler.IReconciler#install(org.eclipse.jface.text.ITextViewer) */ public void install(ITextViewer textViewer) { super.install(textViewer); fPartListener= new PartListener(); IWorkbenchPartSite site= fTextEditor.getSite(); IWorkbenchWindow window= site.getWorkbenchWindow(); window.getPartService().addPartListener(fPartListener); fActivationListener= new ActivationListener(textViewer.getTextWidget()); Shell shell= window.getShell(); shell.addShellListener(fActivationListener); fJavaElementChangedListener= new ElementChangedListener(); JavaScriptCore.addElementChangedListener(fJavaElementChangedListener); fResourceChangeListener= new ResourceChangeListener(); IWorkspace workspace= JavaScriptPlugin.getWorkspace(); workspace.addResourceChangeListener(fResourceChangeListener); fPropertyChangeListener= new IPropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { if (SpellingService.PREFERENCE_SPELLING_ENABLED.equals(event.getProperty()) || SpellingService.PREFERENCE_SPELLING_ENGINE.equals(event.getProperty()) || JavaScriptCore.COMPILER_SEMANTIC_VALIDATION.equals(event.getProperty()) || JavaScriptCore.COMPILER_STRICT_ON_KEYWORD_USAGE.equals(event.getProperty())) forceReconciling(); } }; JavaScriptPlugin.getDefault().getCombinedPreferenceStore().addPropertyChangeListener(fPropertyChangeListener); } /* * @see org.eclipse.jface.text.reconciler.IReconciler#uninstall() */ public void uninstall() { IWorkbenchPartSite site= fTextEditor.getSite(); IWorkbenchWindow window= site.getWorkbenchWindow(); window.getPartService().removePartListener(fPartListener); fPartListener= null; Shell shell= window.getShell(); if (shell != null && !shell.isDisposed()) shell.removeShellListener(fActivationListener); fActivationListener= null; JavaScriptCore.removeElementChangedListener(fJavaElementChangedListener); fJavaElementChangedListener= null; IWorkspace workspace= JavaScriptPlugin.getWorkspace(); workspace.removeResourceChangeListener(fResourceChangeListener); fResourceChangeListener= null; JavaScriptPlugin.getDefault().getCombinedPreferenceStore().removePropertyChangeListener(fPropertyChangeListener); fPropertyChangeListener= null; super.uninstall(); } /* * @see org.eclipse.jface.text.reconciler.AbstractReconciler#forceReconciling() */ protected void forceReconciling() { if (!fIninitalProcessDone) return; super.forceReconciling(); JavaCompositeReconcilingStrategy strategy= (JavaCompositeReconcilingStrategy) getReconcilingStrategy(IDocument.DEFAULT_CONTENT_TYPE); strategy.notifyListeners(false); } /* * @see org.eclipse.jface.text.reconciler.AbstractReconciler#aboutToReconcile() * */ protected void aboutToBeReconciled() { JavaCompositeReconcilingStrategy strategy= (JavaCompositeReconcilingStrategy) getReconcilingStrategy(IDocument.DEFAULT_CONTENT_TYPE); strategy.aboutToBeReconciled(); } /* * @see org.eclipse.jface.text.reconciler.AbstractReconciler#reconcilerReset() */ protected void reconcilerReset() { super.reconcilerReset(); JavaCompositeReconcilingStrategy strategy= (JavaCompositeReconcilingStrategy) getReconcilingStrategy(IDocument.DEFAULT_CONTENT_TYPE); strategy.notifyListeners(true); } /* * @see org.eclipse.jface.text.reconciler.MonoReconciler#initialProcess() */ protected void initialProcess() { synchronized (fMutex) { super.initialProcess(); } fIninitalProcessDone= true; } /* * @see org.eclipse.jface.text.reconciler.MonoReconciler#process(org.eclipse.jface.text.reconciler.DirtyRegion) */ protected void process(DirtyRegion dirtyRegion) { synchronized (fMutex) { fIsReconciling= true; super.process(dirtyRegion); fIsReconciling= false; } } /** * Tells whether the Java Model has changed or not. * * @return <code>true</code> iff the Java Model has changed * */ private synchronized boolean hasJavaModelChanged() { return fHasJavaModelChanged; } /** * Sets whether the Java Model has changed or not. * * @param state <code>true</code> iff the java model has changed * */ private synchronized void setJavaModelChanged(boolean state) { fHasJavaModelChanged= state; } /** * Tells whether this reconciler's editor is active. * * @return <code>true</code> iff the editor is active * */ private synchronized boolean isEditorActive() { return fIsEditorActive; } /** * Sets whether this reconciler's editor is active. * * @param state <code>true</code> iff the editor is active * */ private synchronized void setEditorActive(boolean state) { fIsEditorActive= state; } }