content: /*******************************************************************************
   * Copyright (c) 2000, 2008 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.correction;
  
  import java.util.ConcurrentModificationException;
  import java.util.Iterator;
  
  import org.eclipse.jface.text.BadLocationException;
  import org.eclipse.jface.text.IDocument;
  import org.eclipse.jface.text.ITextSelection;
  import org.eclipse.jface.text.ITextViewer;
  import org.eclipse.jface.text.Position;
  import org.eclipse.jface.text.source.Annotation;
  import org.eclipse.jface.text.source.IAnnotationAccessExtension;
  import org.eclipse.jface.text.source.IAnnotationModel;
  import org.eclipse.jface.text.source.IAnnotationPresentation;
  import org.eclipse.jface.text.source.ImageUtilities;
  import org.eclipse.jface.util.IPropertyChangeListener;
  import org.eclipse.jface.util.PropertyChangeEvent;
  import org.eclipse.swt.SWT;
  import org.eclipse.swt.graphics.GC;
  import org.eclipse.swt.graphics.Image;
  import org.eclipse.swt.graphics.Point;
  import org.eclipse.swt.graphics.Rectangle;
  import org.eclipse.swt.widgets.Canvas;
  import org.eclipse.ui.IEditorPart;
  import org.eclipse.ui.editors.text.EditorsUI;
  import org.eclipse.ui.texteditor.AnnotationPreference;
  import org.eclipse.ui.texteditor.ITextEditor;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.internal.ui.JavaPluginImages;
  import org.eclipse.wst.jsdt.internal.ui.javaeditor.ASTProvider;
  import org.eclipse.wst.jsdt.internal.ui.viewsupport.ISelectionListenerWithAST;
  import org.eclipse.wst.jsdt.internal.ui.viewsupport.SelectionListenerWithASTManager;
  import org.eclipse.wst.jsdt.ui.JavaScriptUI;
  import org.eclipse.wst.jsdt.ui.PreferenceConstants;
  import org.eclipse.wst.jsdt.ui.text.java.IInvocationContext;
  
  /**
   *
   */
  public class QuickAssistLightBulbUpdater {
  
  	public static class AssistAnnotation extends Annotation implements IAnnotationPresentation {
  
  		//XXX: To be fully correct this should be a non-static fields in QuickAssistLightBulbUpdater
  		private static final int LAYER;
  
  		static {
  			Annotation annotation= new Annotation("org.eclipse.wst.jsdt.ui.warning", false, null); //$NON-NLS-1$
  			AnnotationPreference preference= EditorsUI.getAnnotationPreferenceLookup().getAnnotationPreference(annotation);
  			if (preference != null)
  				LAYER= preference.getPresentationLayer() - 1;
  			else
  				LAYER= IAnnotationAccessExtension.DEFAULT_LAYER;
  
  		}
  
  		private Image fImage;
  
  		public AssistAnnotation() {
  		}
  
  		/*
  		 * @see org.eclipse.jface.text.source.IAnnotationPresentation#getLayer()
  		 */
  		public int getLayer() {
  			return LAYER;
  		}
  
  		private Image getImage() {
  			if (fImage == null) {
  				fImage= JavaPluginImages.get(JavaPluginImages.IMG_OBJS_QUICK_ASSIST);
  			}
  			return fImage;
  		}
  
  		/* (non-Javadoc)
  		 * @see org.eclipse.jface.text.source.Annotation#paint(org.eclipse.swt.graphics.GC, org.eclipse.swt.widgets.Canvas, org.eclipse.swt.graphics.Rectangle)
  		 */
  		public void paint(GC gc, Canvas canvas, Rectangle r) {
  			ImageUtilities.drawImage(getImage(), gc, canvas, r, SWT.CENTER, SWT.TOP);
  		}
  
  	}
  
  	private final Annotation fAnnotation;
  	private boolean fIsAnnotationShown;
  	private ITextEditor fEditor;
  	private ITextViewer fViewer;
  
  	private ISelectionListenerWithAST fListener;
  	private IPropertyChangeListener fPropertyChangeListener;
  
  	public QuickAssistLightBulbUpdater(ITextEditor part, ITextViewer viewer) {
  		fEditor= part;
  		fViewer= viewer;
  		fAnnotation= new AssistAnnotation();
  		fIsAnnotationShown= false;
  		fPropertyChangeListener= null;
  	}
  
  	public boolean isSetInPreferences() {
  		return PreferenceConstants.getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_QUICKASSIST_LIGHTBULB);
  	}
  
  	private void installSelectionListener() {
  		fListener= new ISelectionListenerWithAST() {
  			public void selectionChanged(IEditorPart part, ITextSelection selection, JavaScriptUnit astRoot) {
  				doSelectionChanged(selection.getOffset(), selection.getLength(), astRoot);
  			}
  		};
  		SelectionListenerWithASTManager.getDefault().addListener(fEditor, fListener);
  	}
  
  	private void uninstallSelectionListener() {
  		if (fListener != null) {
  			SelectionListenerWithASTManager.getDefault().removeListener(fEditor, fListener);
  			fListener= null;
  		}
  		IAnnotationModel model= getAnnotationModel();
  		if (model != null) {
  			removeLightBulb(model);
  		}
  	}
  
  	public void install() {
  		if (isSetInPreferences()) {
  			installSelectionListener();
  		}
  		if (fPropertyChangeListener == null) {
  			fPropertyChangeListener= new IPropertyChangeListener() {
  				public void propertyChange(PropertyChangeEvent event) {
  					doPropertyChanged(event.getProperty());
  				}
  			};
  			PreferenceConstants.getPreferenceStore().addPropertyChangeListener(fPropertyChangeListener);
  		}
  	}
  
  	public void uninstall() {
  		uninstallSelectionListener();
  		if (fPropertyChangeListener != null) {
  			PreferenceConstants.getPreferenceStore().removePropertyChangeListener(fPropertyChangeListener);
  			fPropertyChangeListener= null;
  		}
  	}
  
  	protected void doPropertyChanged(String property) {
  		if (property.equals(PreferenceConstants.EDITOR_QUICKASSIST_LIGHTBULB)) {
  			if (isSetInPreferences()) {
  				IJavaScriptUnit cu= getCompilationUnit();
  				if (cu != null) {
  					installSelectionListener();
  					Point point= fViewer.getSelectedRange();
  					JavaScriptUnit astRoot= ASTProvider.getASTProvider().getAST(cu, ASTProvider.WAIT_ACTIVE_ONLY, null);
  					if (astRoot != null) {
  						doSelectionChanged(point.x, point.y, astRoot);
  					}
  				}
  			} else {
  				uninstallSelectionListener();
  			}
  		}
  	}
  
  	private IJavaScriptUnit getCompilationUnit() {
  		IJavaScriptElement elem= JavaScriptUI.getEditorInputJavaElement(fEditor.getEditorInput());
  		if (elem instanceof IJavaScriptUnit) {
  			return (IJavaScriptUnit) elem;
  		}
  		return null;
  	}
  
  	private IAnnotationModel getAnnotationModel() {
  		return JavaScriptUI.getDocumentProvider().getAnnotationModel(fEditor.getEditorInput());
  	}
  
  	private IDocument getDocument() {
  		return JavaScriptUI.getDocumentProvider().getDocument(fEditor.getEditorInput());
  	}
  
  
  	private void doSelectionChanged(int offset, int length, JavaScriptUnit astRoot) {
  
  		final IAnnotationModel model= getAnnotationModel();
  		final IJavaScriptUnit cu= getCompilationUnit();
  		if (model == null || cu == null) {
  			return;
  		}
  
  		final AssistContext context= new AssistContext(cu, offset, length);
  		context.setASTRoot(astRoot);
  
  		boolean hasQuickFix= hasQuickFixLightBulb(model, context.getSelectionOffset());
  		if (hasQuickFix) {
  			removeLightBulb(model);
  			return; // there is already a quick fix light bulb at the new location
  		}
  
  		calculateLightBulb(model, context);
  	}
  
  	/*
  	 * Needs to be called synchronized
  	 */
  	private void calculateLightBulb(IAnnotationModel model, IInvocationContext context) {
  		boolean needsAnnotation= JavaCorrectionProcessor.hasAssists(context);
  		if (fIsAnnotationShown) {
  			model.removeAnnotation(fAnnotation);
  		}
  		if (needsAnnotation) {
  			model.addAnnotation(fAnnotation, new Position(context.getSelectionOffset(), context.getSelectionLength()));
  		}
  		fIsAnnotationShown= needsAnnotation;
  	}
  
  	private void removeLightBulb(IAnnotationModel model) {
  		synchronized (this) {
  			if (fIsAnnotationShown) {
  				model.removeAnnotation(fAnnotation);
  				fIsAnnotationShown= false;
  			}
  		}
  	}
  
  	/*
  	 * Tests if there is already a quick fix light bulb on the current line
  	 */
  	private boolean hasQuickFixLightBulb(IAnnotationModel model, int offset) {
  		try {
  			IDocument document= getDocument();
  			if (document == null) {
  				return false;
  			}
  
  			// we access a document and annotation model from within a job
  			// since these are only read accesses, we won't hurt anyone else if
  			// this goes boink
  
  			// may throw an IndexOutOfBoundsException upon concurrent document modification
  			int currLine= document.getLineOfOffset(offset);
  
  			// this iterator is not protected, it may throw ConcurrentModificationExceptions
  			Iterator iter= model.getAnnotationIterator();
  			while (iter.hasNext()) {
  				Annotation annot= (Annotation) iter.next();
  				if (JavaCorrectionProcessor.isQuickFixableType(annot)) {
  					// may throw an IndexOutOfBoundsException upon concurrent annotation model changes
  					Position pos= model.getPosition(annot);
  					if (pos != null) {
  						// may throw an IndexOutOfBoundsException upon concurrent document modification
  						int startLine= document.getLineOfOffset(pos.getOffset());
  						if (startLine == currLine && JavaCorrectionProcessor.hasCorrections(annot)) {
  							return true;
  						}
  					}
  				}
  			}
  		} catch (BadLocationException e) {
  			// ignore
  		} catch (IndexOutOfBoundsException e) {
  			// concurrent modification - too bad, ignore
  		} catch (ConcurrentModificationException e) {
  			// concurrent modification - too bad, ignore
  		}
  		return false;
  	}
  
  
  }
  