content: /*******************************************************************************
   * Copyright (c) 2000, 2013 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.javaeditor;
  
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.NullProgressMonitor;
  import org.eclipse.jface.dialogs.MessageDialog;
  import org.eclipse.jface.text.ISynchronizable;
  import org.eclipse.jface.text.Position;
  import org.eclipse.jface.text.source.Annotation;
  import org.eclipse.jface.text.source.IAnnotationModel;
  import org.eclipse.jface.text.source.IAnnotationModelExtension;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.dom.ASTNode;
  import org.eclipse.wst.jsdt.core.dom.ASTVisitor;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.IFunctionBinding;
  import org.eclipse.wst.jsdt.core.dom.ITypeBinding;
  import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration;
  import org.eclipse.wst.jsdt.core.dom.SimpleName;
  import org.eclipse.wst.jsdt.internal.corext.dom.Bindings;
  import org.eclipse.wst.jsdt.internal.corext.util.JdtFlags;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.text.java.IJavaReconcilingListener;
  import org.eclipse.wst.jsdt.internal.ui.util.ExceptionHandler;
  import org.eclipse.wst.jsdt.ui.JavaScriptUI;
  
  /**
   * Manages the override and overwrite indicators for
   * the given Java element and annotation model.
   *
   * 
   */
  class OverrideIndicatorManager implements IJavaReconcilingListener {
  
  	/**
  	 * Overwrite and override indicator annotation.
  	 *
  	 * 
  	 */
  	class OverrideIndicator extends Annotation {
  
  		private boolean fIsOverwriteIndicator;
  		private String fAstNodeKey;
  
  		/**
  		 * Creates a new override annotation.
  		 *
  		 * @param isOverwriteIndicator <code>true</code> if this annotation is
  		 *            an overwrite indicator, <code>false</code> otherwise
  		 * @param text the text associated with this annotation
  		 * @param key the method binding key
  		 * 
  		 */
  		OverrideIndicator(boolean isOverwriteIndicator, String text, String key) {
  			super(ANNOTATION_TYPE, false, text);
  			fIsOverwriteIndicator= isOverwriteIndicator;
  			fAstNodeKey= key;
  		}
  
  		/**
  		 * Tells whether this is an overwrite or an override indicator.
  		 *
  		 * @return <code>true</code> if this is an overwrite indicator
  		 */
  		public boolean isOverwriteIndicator() {
  			return fIsOverwriteIndicator;
  		}
  
  		/**
  		 * Opens and reveals the defining method.
  		 */
  		public void open() {
  			JavaScriptUnit ast= ASTProvider.getASTProvider().getAST(fJavaElement, ASTProvider.WAIT_ACTIVE_ONLY, null);
  			if (ast != null) {
  				ASTNode node= ast.findDeclaringNode(fAstNodeKey);
  				if (node instanceof FunctionDeclaration) {
  					try {
  						IFunctionBinding methodBinding= ((FunctionDeclaration)node).resolveBinding();
  						IFunctionBinding definingMethodBinding= Bindings.findOverriddenMethod(methodBinding, true);
  						if (definingMethodBinding != null) {
  							IJavaScriptElement definingMethod= definingMethodBinding.getJavaElement();
  							if (definingMethod != null) {
  								JavaScriptUI.openInEditor(definingMethod, true, true);
  								return;
  							}
  						}
  					} catch (CoreException e) {
  						ExceptionHandler.handle(e, JavaEditorMessages.OverrideIndicatorManager_open_error_title, JavaEditorMessages.OverrideIndicatorManager_open_error_messageHasLogEntry);
  						return;
  					}
  				}
  			}
  			String title= JavaEditorMessages.OverrideIndicatorManager_open_error_title;
  			String message= JavaEditorMessages.OverrideIndicatorManager_open_error_message;
  			MessageDialog.openError(JavaScriptPlugin.getActiveWorkbenchShell(), title, message);
  		}
  	}
  
  	static final String ANNOTATION_TYPE= "org.eclipse.wst.jsdt.ui.overrideIndicator"; //$NON-NLS-1$
  
  	private IAnnotationModel fAnnotationModel;
  	private Object fAnnotationModelLockObject;
  	private Annotation[] fOverrideAnnotations;
  	private IJavaScriptElement fJavaElement;
  
  
  	public OverrideIndicatorManager(IAnnotationModel annotationModel, IJavaScriptElement javaElement, JavaScriptUnit ast) {
  		Assert.isNotNull(annotationModel);
  		Assert.isNotNull(javaElement);
  
  		fJavaElement= javaElement;
  		fAnnotationModel=annotationModel;
  		fAnnotationModelLockObject= getLockObject(fAnnotationModel);
  
  		updateAnnotations(ast, new NullProgressMonitor());
  	}
  
  	/**
  	 * Returns the lock object for the given annotation model.
  	 *
  	 * @param annotationModel the annotation model
  	 * @return the annotation model's lock object
  	 * 
  	 */
  	private Object getLockObject(IAnnotationModel annotationModel) {
  		if (annotationModel instanceof ISynchronizable) {
  			Object lock= ((ISynchronizable)annotationModel).getLockObject();
  			if (lock != null)
  				return lock;
  		}
  		return annotationModel;
  	}
  
  	/**
  	 * Updates the override and implements annotations based
  	 * on the given AST.
  	 *
  	 * @param ast the compilation unit AST
  	 * @param progressMonitor the progress monitor
  	 * 
  	 */
  	protected void updateAnnotations(JavaScriptUnit ast, IProgressMonitor progressMonitor) {
  
  		if (ast == null || progressMonitor.isCanceled())
  			return;
  
  		final Map annotationMap= new HashMap(50);
  
  		ast.accept(new ASTVisitor(false) {
  			/*
  			 * @see org.eclipse.wst.jsdt.core.dom.ASTVisitor#visit(org.eclipse.wst.jsdt.core.dom.FunctionDeclaration)
  			 */
  			public boolean visit(FunctionDeclaration node) {
  				IFunctionBinding binding= node.resolveBinding();
  				if (binding != null) {
  					IFunctionBinding definingMethod= Bindings.findOverriddenMethod(binding, true);
  					if (definingMethod != null) {
  
  						ITypeBinding definingType= definingMethod.getDeclaringClass();
  						String qualifiedMethodName= definingType.getQualifiedName() + "." + binding.getName(); //$NON-NLS-1$
  
  						boolean isImplements= JdtFlags.isAbstract(definingMethod);
  						String text;
  						if (isImplements)
  							text= Messages.format(JavaEditorMessages.OverrideIndicatorManager_implements, qualifiedMethodName);
  						else
  							text= Messages.format(JavaEditorMessages.OverrideIndicatorManager_overrides, qualifiedMethodName);
  
  						SimpleName name= node.getName();
  						Position position= name != null ?
  									new Position(name.getStartPosition(), name.getLength()) :
  										new Position(node.getStartPosition());
  
  						annotationMap.put(
  								new OverrideIndicator(isImplements, text, binding.getKey()), 
  								position);
  
  					}
  				}
  				return true;
  			}
  		});
  
  		if (progressMonitor.isCanceled())
  			return;
  
  		synchronized (fAnnotationModelLockObject) {
  			if (fAnnotationModel instanceof IAnnotationModelExtension) {
  				((IAnnotationModelExtension)fAnnotationModel).replaceAnnotations(fOverrideAnnotations, annotationMap);
  			} else {
  				removeAnnotations();
  				Iterator iter= annotationMap.entrySet().iterator();
  				while (iter.hasNext()) {
  					Map.Entry mapEntry= (Map.Entry)iter.next();
  					fAnnotationModel.addAnnotation((Annotation)mapEntry.getKey(), (Position)mapEntry.getValue());
  				}
  			}
  			fOverrideAnnotations= (Annotation[])annotationMap.keySet().toArray(new Annotation[annotationMap.keySet().size()]);
  		}
  	}
  
  	/**
  	 * Removes all override indicators from this manager's annotation model.
  	 */
  	void removeAnnotations() {
  		if (fOverrideAnnotations == null)
  			return;
  
  		synchronized (fAnnotationModelLockObject) {
  			if (fAnnotationModel instanceof IAnnotationModelExtension) {
  				((IAnnotationModelExtension)fAnnotationModel).replaceAnnotations(fOverrideAnnotations, null);
  			} else {
  				for (int i= 0, length= fOverrideAnnotations.length; i < length; i++)
  					fAnnotationModel.removeAnnotation(fOverrideAnnotations[i]);
  			}
  			fOverrideAnnotations= null;
  		}
  	}
  
  	/*
  	 * @see org.eclipse.wst.jsdt.internal.ui.text.java.IJavaReconcilingListener#aboutToBeReconciled()
  	 */
  	public void aboutToBeReconciled() {
  	}
  
  	/*
  	 * @see org.eclipse.wst.jsdt.internal.ui.text.java.IJavaReconcilingListener#reconciled(JavaScriptUnit, boolean, IProgressMonitor)
  	 */
  	public void reconciled(JavaScriptUnit ast, boolean forced, IProgressMonitor progressMonitor) {
  		updateAnnotations(ast, progressMonitor);
  	}
  }
  
  