/*******************************************************************************
* Copyright (c) 2010, 2015 Tomasz Wesolowski 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:
* Tomasz Wesolowski - initial API and implementation
* Sergey Prigogin (Google)
* Patrick Hofer [bug 345872]
* Nathan Ridge [bug 345872]
*******************************************************************************/
package org.eclipse.cdt.internal.ui.editor;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
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.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.DOMException;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBase;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.parser.util.ArrayUtil;
import org.eclipse.cdt.ui.CDTUITools;
import org.eclipse.cdt.internal.core.dom.parser.ASTQueries;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ClassTypeHelper;
import org.eclipse.cdt.internal.core.model.ASTStringUtil;
import org.eclipse.cdt.internal.core.model.ext.ICElementHandle;
import org.eclipse.cdt.internal.ui.text.ICReconcilingListener;
import org.eclipse.cdt.internal.ui.viewsupport.IndexUI;
public class OverrideIndicatorManager implements ICReconcilingListener {
public static final String ANNOTATION_TYPE = "org.eclipse.cdt.ui.overrideIndicator"; //$NON-NLS-1$
private final HashMap<ICPPClassType, ICPPMethod[]> methodsCache = new HashMap<>();
public static final int ANNOTATION_IMPLEMENTS = 0;
public static final int ANNOTATION_OVERRIDES = 1;
public static final int ANNOTATION_SHADOWS = 2;
public class OverrideIndicator extends Annotation {
public static final String ANNOTATION_TYPE_ID = "org.eclipse.cdt.ui.overrideIndicator"; //$NON-NLS-1$
private final int type;
private final ICElementHandle elementHandle;
public OverrideIndicator(int resultType, String message, ICElementHandle elementHandle) {
super(ANNOTATION_TYPE_ID, false, message);
this.type = resultType;
this.elementHandle = elementHandle;
}
public int getIndicationType() {
return type;
}
public void open() {
try {
CDTUITools.openInEditor(elementHandle, true, true);
} catch (CoreException e) {
}
}
}
private final IAnnotationModel fAnnotationModel;
private Annotation[] fOverrideAnnotations;
private final Object fAnnotationModelLockObject;
private int annotationKind;
private String annotationMessage;
public OverrideIndicatorManager(IAnnotationModel annotationModel, IASTTranslationUnit ast) {
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;
}
protected void updateAnnotations(IASTTranslationUnit ast, IProgressMonitor progressMonitor) {
if (ast == null || progressMonitor.isCanceled())
return;
final IIndex index = ast.getIndex();
final Map<Annotation, Position> annotationMap= new HashMap<Annotation, Position>(50);
class MethodFinder extends ASTVisitor {
{
shouldVisitDeclarators = true;
}
@Override
public int visit(IASTDeclarator declarator) {
if (!(declarator instanceof ICPPASTFunctionDeclarator)) {
return PROCESS_CONTINUE;
}
IASTDeclarator decl = ASTQueries.findInnermostDeclarator(declarator);
IASTName name = decl.getName();
if (name != null) {
IBinding binding = name.resolveBinding();
if (binding instanceof ICPPMethod) {
ICPPMethod method = (ICPPMethod) binding;
try {
ICPPMethod overriddenMethod = testForOverride(method, declarator);
if (overriddenMethod != null) {
try {
ICElementHandle baseDeclaration = IndexUI.findAnyDeclaration(index, null, overriddenMethod);
if (baseDeclaration == null) {
ICElementHandle[] allDefinitions = IndexUI.findAllDefinitions(index, overriddenMethod);
if (allDefinitions.length > 0) {
baseDeclaration = allDefinitions[0];
}
}
OverrideIndicator indicator = new OverrideIndicator(annotationKind, annotationMessage, baseDeclaration);
IASTFileLocation fileLocation = declarator.getFileLocation();
Position position = new Position(fileLocation.getNodeOffset(), fileLocation.getNodeLength());
annotationMap.put(indicator, position);
} catch (CoreException e) {
}
}
} catch (DOMException e) {
}
}
}
return PROCESS_CONTINUE;
}
}
try {
ast.accept(new MethodFinder());
} finally {
methodsCache.clear();
}
if (progressMonitor.isCanceled())
return;
synchronized (fAnnotationModelLockObject) {
if (fAnnotationModel instanceof IAnnotationModelExtension) {
((IAnnotationModelExtension)fAnnotationModel).replaceAnnotations(fOverrideAnnotations, annotationMap);
} else {
removeAnnotations();
for (Map.Entry<Annotation, Position> entry : annotationMap.entrySet()) {
fAnnotationModel.addAnnotation(entry.getKey(), entry.getValue());
}
}
fOverrideAnnotations= annotationMap.keySet().toArray(new Annotation[annotationMap.keySet().size()]);
}
}
private ICPPMethod testForOverride(ICPPMethod method, IASTNode point) throws DOMException {
if (method.isDestructor() || method.isPureVirtual()) {
return null;
}
ICPPBase[] bases = ClassTypeHelper.getBases(method.getClassOwner(), point);
if (bases.length == 0) {
return null;
}
ICPPClassType owningClass = method.getClassOwner();
ICPPMethod overriddenMethod = getOverriddenMethodInBaseClass(owningClass, method, point);
if (overriddenMethod != null) {
StringBuilder sb = new StringBuilder();
if (annotationKind == ANNOTATION_IMPLEMENTS) {
sb.append(CEditorMessages.OverrideIndicatorManager_implements);
} else if (annotationKind == ANNOTATION_OVERRIDES){
sb.append(CEditorMessages.OverrideIndicatorManager_overrides);
} else if (annotationKind == ANNOTATION_SHADOWS) {
sb.append(CEditorMessages.OverrideIndicatorManager_shadows);
}
sb.append(' ');
sb.append(ASTStringUtil.join(overriddenMethod.getQualifiedName(), "::")); //$NON-NLS-1$
if (bases.length > 1) {
boolean foundInDirectlyDerivedBaseClass = false;
ICPPClassType matchedMethodOwner = overriddenMethod.getClassOwner();
for (ICPPBase base : bases) {
if (base.getBaseClass() == matchedMethodOwner) {
foundInDirectlyDerivedBaseClass = true;
break;
}
}
if (!foundInDirectlyDerivedBaseClass) {
ICPPClassType indirectingClass = null;
for (ICPPBase base : bases) {
IBinding baseClass = base.getBaseClass();
if (baseClass instanceof ICPPClassType) {
indirectingClass = (ICPPClassType) baseClass;
if (getOverriddenMethodInBaseClass(indirectingClass, method, point) != null)
break;
}
}
if (indirectingClass != null) {
sb.append(' ');
sb.append(CEditorMessages.OverrideIndicatorManager_via);
sb.append(' ');
sb.append(ASTStringUtil.join(indirectingClass.getQualifiedName(), "::")); //$NON-NLS-1$
}
}
}
annotationMessage = sb.toString();
return overriddenMethod;
}
return null;
}
private ICPPMethod getOverriddenMethodInBaseClass(ICPPClassType aClass, ICPPMethod testedMethod,
IASTNode point) throws DOMException {
final String testedMethodName = testedMethod.getName();
ICPPMethod[] allInheritedMethods;
if (methodsCache.containsKey(aClass)) {
allInheritedMethods = methodsCache.get(aClass);
} else {
ICPPMethod[] inheritedMethods = null;
ICPPClassType[] bases= ClassTypeHelper.getAllBases(aClass, point);
for (ICPPClassType base : bases) {
inheritedMethods = ArrayUtil.addAll(ICPPMethod.class, inheritedMethods,
ClassTypeHelper.getDeclaredMethods(base, point));
}
allInheritedMethods = ArrayUtil.trim(ICPPMethod.class, inheritedMethods);
methodsCache.put(aClass, allInheritedMethods);
}
boolean foundOverridden = false;
ICPPMethod result = null;
for (ICPPMethod method : allInheritedMethods) {
if (method.getName().equals(testedMethodName)) {
if (method.isVirtual()) {
if (ClassTypeHelper.isOverrider(testedMethod, method)) {
if (method.isPureVirtual()) {
annotationKind = ANNOTATION_IMPLEMENTS;
result = method;
} else {
annotationKind = ANNOTATION_OVERRIDES;
result = method;
}
foundOverridden = true;
} else if (!foundOverridden) {
// The method has same name as virtual method in base, but does not override
// it (e.g. because it has a different signature), it shadows it.
annotationKind = ANNOTATION_SHADOWS;
result = method;
}
} else if (!foundOverridden) {
// The method has same name and is not virtual, it hides/shadows the method
// in the base class.
annotationKind = ANNOTATION_SHADOWS;
result = method;
}
}
}
return result;
}
/**
* 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;
}
}
@Override
public void aboutToBeReconciled() {
}
@Override
public void reconciled(IASTTranslationUnit ast, boolean force, IProgressMonitor progressMonitor) {
updateAnnotations(ast, progressMonitor);
}
}