/******************************************************************************* * Copyright (c) 2010 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 *******************************************************************************/ package org.eclipse.cdt.internal.ui.editor; import java.util.HashSet; import java.util.Set; import java.util.Vector; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; 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.cdt.core.dom.ast.ASTVisitor; import org.eclipse.cdt.core.dom.ast.DOMException; import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier; import org.eclipse.cdt.core.dom.ast.IASTDeclaration; import org.eclipse.cdt.core.dom.ast.IASTDeclarator; import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator; import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.IType; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier; import org.eclipse.cdt.core.dom.ast.cpp.ICPPBase; import org.eclipse.cdt.core.dom.ast.cpp.ICPPBinding; import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionType; import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod; import org.eclipse.cdt.core.index.IIndex; 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 { static final String ANNOTATION_TYPE = "org.eclipse.cdt.ui.overrideIndicator"; //$NON-NLS-1$ private static final String MESSAGE_SEPARATOR = ";\n"; //$NON-NLS-1$ public static class OverrideInfo { public int nodeOffset; public int resultType; public String message; public int nodeLength; public IBinding binding; public OverrideInfo(int nodeOffset, int nodeLength, int markerType, String message, IBinding binding) { this.nodeOffset = nodeOffset; this.resultType = markerType; this.message = message; this.binding = binding; } } public static final int RESULT_OVERRIDES = 0; public static final int RESULT_IMPLEMENTS = 1; public static final int RESULT_SHADOWS = 2; public class OverrideIndicator extends Annotation { public static final String ANNOTATION_TYPE_ID = "org.eclipse.cdt.ui.overrideIndicator"; //$NON-NLS-1$ private int type; private ICElementHandle declaration; public OverrideIndicator(int resultType, String message, IBinding binding, IIndex index) { super(ANNOTATION_TYPE_ID, false, message); this.type = resultType; try { declaration = IndexUI.findAnyDeclaration(index, null, binding); if (declaration == null) { ICElementHandle[] allDefinitions = IndexUI.findAllDefinitions(index, binding); if (allDefinitions.length > 0) { declaration = allDefinitions[0]; } } } catch (CoreException e) { } } public int getIndicationType() { return type; } public void open() { try { CDTUITools.openInEditor(declaration, true, true); } catch (CoreException e) { } } } private IAnnotationModel fAnnotationModel; private Vector<OverrideIndicator> fOverrideAnnotations = new Vector<OverrideIndicator>(); private Object fAnnotationModelLockObject; public OverrideIndicatorManager(IAnnotationModel annotationModel) { fAnnotationModel = annotationModel; fAnnotationModelLockObject = getLockObject(fAnnotationModel); } private void handleResult(OverrideInfo info, IIndex index) { Position position = new Position(info.nodeOffset, info.nodeLength); OverrideIndicator indicator = new OverrideIndicator(info.resultType, info.message, info.binding, index); synchronized (fAnnotationModelLockObject) { fAnnotationModel.addAnnotation(indicator, position); } fOverrideAnnotations.add(indicator); } /** * Removes all override indicators from this manager's annotation model. */ public void removeAnnotations() { if (fOverrideAnnotations == null) return; synchronized (fAnnotationModelLockObject) { for (Annotation i : fOverrideAnnotations) fAnnotationModel.removeAnnotation(i); fOverrideAnnotations.clear(); } } public void generateAnnotations(IASTTranslationUnit ast, final IIndex index) { class MethodDeclarationFinder extends ASTVisitor { { shouldVisitDeclarations = true; } @Override public int visit(IASTDeclaration declaration) { try { IBinding binding = null; ICPPMethod method = null; if (isFunctionDeclaration(declaration)) { binding = getDeclarationBinding(declaration); } else if (declaration instanceof IASTFunctionDefinition) { binding = getDefinitionBinding((IASTFunctionDefinition) declaration); } if (binding instanceof ICPPMethod) { method = (ICPPMethod) binding; OverrideInfo overrideInfo = testForOverride(method, declaration.getFileLocation()); if (overrideInfo != null) { handleResult(overrideInfo, index); } } } catch (DOMException e) { } // go to next declaration return PROCESS_SKIP; } } class CompositeTypeFinder extends ASTVisitor { { shouldVisitDeclSpecifiers = true; } @Override public int visit(IASTDeclSpecifier declSpec) { if (declSpec instanceof ICPPASTCompositeTypeSpecifier) { declSpec.accept(new MethodDeclarationFinder()); } return PROCESS_CONTINUE; } } class MethodDefinitionFinder extends ASTVisitor { { shouldVisitDeclarations = true; } @Override public int visit(IASTDeclaration declaration) { try { if (!(declaration instanceof IASTFunctionDefinition)) { return PROCESS_SKIP; } IASTFunctionDefinition definition = (IASTFunctionDefinition) declaration; IBinding definitionBinding = getDefinitionBinding(definition); if (!(definitionBinding instanceof ICPPMethod)) { return PROCESS_SKIP; } ICPPMethod method = (ICPPMethod) definitionBinding; OverrideInfo overrideInfo = testForOverride(method, definition.getFileLocation()); if (overrideInfo != null) { handleResult(overrideInfo, index); } } catch (DOMException e) { } return PROCESS_SKIP; } } ast.accept(new CompositeTypeFinder()); ast.accept(new MethodDefinitionFinder()); } public static OverrideInfo testForOverride(ICPPMethod testedOverride, IASTFileLocation location) throws DOMException { testedOverride.getClassOwner().getBases(); boolean onlyPureVirtual = true; StringBuilder sb = new StringBuilder(); Set<ICPPMethod> overridenMethods = new HashSet<ICPPMethod>(); Set<ICPPMethod> shadowedMethods = new HashSet<ICPPMethod>(); Set<ICPPClassType> alreadyTestedBases = new HashSet<ICPPClassType>(); ICPPBase[] bases = testedOverride.getClassOwner().getBases(); // Don't override 'self' in cyclic inheritance alreadyTestedBases.add(testedOverride.getClassOwner()); for (ICPPBase base : bases) { ICPPClassType testedClass; if (!(base.getBaseClass() instanceof ICPPClassType)) { continue; } testedClass = (ICPPClassType) base.getBaseClass(); overridenMethods.clear(); shadowedMethods.clear(); handleBaseClass(testedClass, testedOverride, overridenMethods, shadowedMethods, alreadyTestedBases); for (ICPPMethod overriddenMethod : overridenMethods) { if (sb.length() > 0) { sb.append(MESSAGE_SEPARATOR); } if (overriddenMethod.isPureVirtual()) { sb.append(CEditorMessages.OverrideIndicatorManager_implements); } else { sb.append(CEditorMessages.OverrideIndicatorManager_overrides); onlyPureVirtual = false; } sb.append(' '); sb.append(getQualifiedNameString(overriddenMethod)); if (bases.length > 1 && overriddenMethod.getClassOwner() != testedClass) { sb.append(' '); sb.append(CEditorMessages.OverrideIndicatorManager_via); sb.append(' '); sb.append(getQualifiedNameString(testedClass)); } } for (ICPPMethod shadowedMethod : shadowedMethods) { if (sb.length() > 0) { sb.append(MESSAGE_SEPARATOR); } sb.append(CEditorMessages.OverrideIndicatorManager_shadows); sb.append(' '); sb.append(getQualifiedNameString(shadowedMethod)); } } int markerType; if (overridenMethods.size() > 0) { markerType = onlyPureVirtual ? RESULT_IMPLEMENTS : RESULT_OVERRIDES; } else { markerType = RESULT_SHADOWS; } IBinding bindingToOpen = null; if (overridenMethods.size() > 0) { bindingToOpen = overridenMethods.iterator().next(); } else if (shadowedMethods.size() > 0) { bindingToOpen = shadowedMethods.iterator().next(); } if (sb.length() > 0) { OverrideInfo info = new OverrideInfo(location.getNodeOffset(), location.getNodeLength(), markerType, sb.toString(), bindingToOpen); return info; } return null; } /** * If the class directly has a valid override for testedOverride, it is added to foundBindings. Otherwise * each base class is added to handleBaseClass. * * @param shadowedMethods * @param alreadyTestedBases * * @throws DOMException */ private static void handleBaseClass(ICPPClassType aClass, ICPPMethod testedOverride, Set<ICPPMethod> foundMethods, Set<ICPPMethod> shadowedMethods, Set<ICPPClassType> alreadyTestedBases) throws DOMException { if (alreadyTestedBases.contains(aClass)) { return; } else { alreadyTestedBases.add(aClass); } Vector<ICPPMethod> validOverrides = new Vector<ICPPMethod>(); for (ICPPMethod method : aClass.getDeclaredMethods()) { if (testedOverride.getName().equals(method.getName())) { if (ClassTypeHelper.isOverrider(testedOverride, method)) { validOverrides.add(method); } else if (sameParameters(testedOverride, method)) { shadowedMethods.add(method); } } } if (validOverrides.size() > 1) { /* System.err.println("Found many valid overrides"); */ } if (validOverrides.size() >= 1) { foundMethods.addAll(validOverrides); return; } for (ICPPBase b : aClass.getBases()) { if (!(b.getBaseClass() instanceof ICPPClassType)) { continue; } ICPPClassType baseClass = (ICPPClassType) b.getBaseClass(); handleBaseClass(baseClass, testedOverride, foundMethods, shadowedMethods, alreadyTestedBases); } } private static boolean sameParameters(ICPPMethod a, ICPPMethod b) throws DOMException { ICPPFunctionType aType = a.getType(); ICPPFunctionType bType = b.getType(); if (aType.getParameterTypes().length != bType.getParameterTypes().length) { return false; } for (int i = 0; i < aType.getParameterTypes().length; ++i) { IType overrideParamType = aType.getParameterTypes()[i]; IType methodParamType = bType.getParameterTypes()[i]; if (!overrideParamType.isSameType(methodParamType)) { return false; } } return true; } private static String getQualifiedNameString(ICPPBinding binding) throws DOMException { String methodQualifiedName = ASTStringUtil.join(binding.getQualifiedName(), "::"); //$NON-NLS-1$ return methodQualifiedName; } private static boolean isFunctionDeclaration(IASTDeclaration declaration) { if (!(declaration instanceof IASTSimpleDeclaration)) { return false; } IASTSimpleDeclaration simpleDecl = (IASTSimpleDeclaration) declaration; IASTDeclarator[] declarators = simpleDecl.getDeclarators(); if (declarators.length < 1) { return false; } IASTDeclarator declarator = ASTQueries.findInnermostDeclarator(declarators[0]); return (declarator instanceof IASTFunctionDeclarator); } private static IBinding getDefinitionBinding(IASTFunctionDefinition definition) { IASTDeclarator declarator = ASTQueries.findInnermostDeclarator(definition.getDeclarator()); return declarator.getName().resolveBinding(); } private static IBinding getDeclarationBinding(IASTDeclaration declaration) { for (IASTNode node : declaration.getChildren()) { if (node instanceof IASTDeclarator) { IASTDeclarator decl = ASTQueries.findInnermostDeclarator((IASTDeclarator) node); return decl.getName().resolveBinding(); } } return null; } public void aboutToBeReconciled() { } public void reconciled(IASTTranslationUnit ast, boolean force, IProgressMonitor progressMonitor) { if (ast == null) { return; } IIndex index = ast.getIndex(); removeAnnotations(); generateAnnotations(ast, index); } /** * 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; } }