/******************************************************************************* * Copyright (c) 2000, 2015 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 * Mohamed Azab (Mentor Graphics) - Bug 438549. Add mechanism for parameter guessing. *******************************************************************************/ package org.eclipse.cdt.internal.ui.text.contentassist; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.swt.graphics.Image; import org.eclipse.cdt.core.dom.ast.DOMException; import org.eclipse.cdt.core.dom.ast.IASTExpression.ValueCategory; 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.ICompositeType; import org.eclipse.cdt.core.dom.ast.IEnumeration; import org.eclipse.cdt.core.dom.ast.IEnumerator; import org.eclipse.cdt.core.dom.ast.IFunction; import org.eclipse.cdt.core.dom.ast.IPointerType; import org.eclipse.cdt.core.dom.ast.IType; import org.eclipse.cdt.core.dom.ast.ITypedef; import org.eclipse.cdt.core.dom.ast.IVariable; 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.ICPPClassSpecialization; import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassTemplate; import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; import org.eclipse.cdt.core.dom.ast.cpp.ICPPField; import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionTemplate; import org.eclipse.cdt.core.dom.ast.cpp.ICPPMember; import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod; import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespace; import org.eclipse.cdt.core.dom.ast.cpp.ICPPUsingDeclaration; import org.eclipse.cdt.core.model.CModelException; import org.eclipse.cdt.core.model.IField; import org.eclipse.cdt.core.parser.ast.ASTAccessVisibility; import org.eclipse.cdt.core.parser.util.CharArrayUtils; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.Conversions; import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.Conversions.Context; import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.Conversions.UDCMode; import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.Cost; import org.eclipse.cdt.internal.ui.viewsupport.CElementImageProvider; /** * This class is based on org.eclipse.jdt.internal.ui.text.java.ParameterGuesser * * This class produces a logically-ordered list of applicable variables for later use as parameter guessing * proposals for a function parameter. */ public class ParameterGuesser { private final Set<String> fAlreadyMatchedNames = new HashSet<>(); /** * Variable type. Used to choose the best guess based on scope (LOCAL is preferred over FIELD, * which is preferred over GLOBAL). */ static enum VariableType { LOCAL(0), FIELD(1), GLOBAL(3); // Give the global variables a lower priority. private final int priority; private VariableType(int priority) { this.priority = priority; } public int getPriority() { return priority; } } private final static class Variable { public final String name; public final VariableType variableType; public final int positionScore; public int totalScore; public final char[] triggerChars; public final ImageDescriptor descriptor; public boolean alreadyMatched; public Variable(String name, VariableType variableType, int positionScore, char[] triggerChars, ImageDescriptor descriptor) { this.name = name; this.variableType = variableType; this.positionScore = positionScore; this.triggerChars = triggerChars; this.descriptor = descriptor; } } private Collection<Variable> evaluateVisibleMatches(IType expectedType, List<IBinding> suggestions, IASTTranslationUnit ast) throws CModelException { Set<Variable> res = new HashSet<>(); int size = suggestions.size(); for (int i = 0; i < size; i++) { Variable variable = createVariable(suggestions.get(i), expectedType, i, ast); if (variable != null) { if (fAlreadyMatchedNames.contains(variable.name)) { variable.alreadyMatched = true; } res.add(variable); } } return res; } private boolean isAnonymousBinding(IBinding binding) { char[] name = binding.getNameCharArray(); return name.length == 0 || name[0] == '{'; } protected IType getType(IBinding binding) { if (!isAnonymousBinding(binding) && binding instanceof IVariable) return ((IVariable) binding).getType(); return null; } private Variable createVariable(IBinding element, IType enclosingType, int positionScore, IASTTranslationUnit ast) throws CModelException { IType elementType = getType(element); String elementName = element.getName(); if (elementType != null && (elementType.toString().equals(enclosingType.toString()) || elementType.isSameType(enclosingType) || isImplicitlyConvertible(enclosingType, elementType, ast) || isParent(elementType, enclosingType) || isReferenceTo(enclosingType, elementType) || isReferenceTo(elementType, enclosingType))) { VariableType variableType = VariableType.GLOBAL; if (element instanceof ICPPField) { variableType = VariableType.FIELD; } else if (element instanceof IVariable) { try { if (element instanceof ICPPBinding && ((ICPPBinding) element).isGloballyQualified()) { variableType = VariableType.GLOBAL; } else { variableType = VariableType.LOCAL; } } catch (DOMException e) { } } // Handle reference case if (isReferenceTo(enclosingType, elementType)) elementName = "&" + elementName; //$NON-NLS-1$ else if (isReferenceTo(elementType, enclosingType)) elementName = "*" + elementName; //$NON-NLS-1$ return new Variable(elementName, variableType, positionScore, CharArrayUtils.EMPTY_CHAR_ARRAY, getImageDescriptor(element)); } return null; } private boolean isReferenceTo(IType ref, IType val) { if (ref instanceof IPointerType) { IType ptr = ((IPointerType) ref).getType(); if (ptr.toString().equals(val.toString()) || ptr.isSameType(val)) return true; } return false; } private boolean isImplicitlyConvertible(IType orginType, IType candidateType, IASTNode point) { try { Cost cost = Conversions.checkImplicitConversionSequence(orginType, candidateType, ValueCategory.LVALUE, UDCMode.ALLOWED, Context.ORDINARY, point); if (cost.converts()) return true; } catch (DOMException e) { return false; } return false; } /** * Returns true, if the parent type is a direct/indirect parent of the child type */ private boolean isParent(IType child, IType parent) { if (child != null && parent != null && child instanceof ICPPClassType && !(child instanceof ICPPClassSpecialization) && parent instanceof ICPPClassType && !(parent instanceof ICPPClassSpecialization)) { ICPPBase[] bases = ((ICPPClassType) child).getBases(); for (ICPPBase base : bases) { IType tmpType = base.getBaseClassType(); if (tmpType.toString().equals(parent.toString()) || tmpType.isSameType(parent) || isParent(tmpType, parent)) return true; } } return false; } private ImageDescriptor getImageDescriptor(IBinding binding) { ImageDescriptor imageDescriptor = null; if (binding instanceof ITypedef) { imageDescriptor = CElementImageProvider.getTypedefImageDescriptor(); } else if (binding instanceof ICompositeType) { if (((ICompositeType) binding).getKey() == ICPPClassType.k_class || binding instanceof ICPPClassTemplate) imageDescriptor = CElementImageProvider.getClassImageDescriptor(); else if (((ICompositeType) binding).getKey() == ICompositeType.k_struct) imageDescriptor = CElementImageProvider.getStructImageDescriptor(); else if (((ICompositeType) binding).getKey() == ICompositeType.k_union) imageDescriptor = CElementImageProvider.getUnionImageDescriptor(); } else if (binding instanceof ICPPMethod) { switch (((ICPPMethod) binding).getVisibility()) { case ICPPMember.v_private: imageDescriptor = CElementImageProvider.getMethodImageDescriptor(ASTAccessVisibility.PRIVATE); break; case ICPPMember.v_protected: imageDescriptor = CElementImageProvider.getMethodImageDescriptor(ASTAccessVisibility.PROTECTED); break; default: imageDescriptor = CElementImageProvider.getMethodImageDescriptor(ASTAccessVisibility.PUBLIC); break; } } else if (binding instanceof IFunction) { imageDescriptor = CElementImageProvider.getFunctionImageDescriptor(); } else if (binding instanceof ICPPField) { switch (((ICPPField) binding).getVisibility()) { case ICPPMember.v_private: imageDescriptor = CElementImageProvider.getFieldImageDescriptor(ASTAccessVisibility.PRIVATE); break; case ICPPMember.v_protected: imageDescriptor = CElementImageProvider.getFieldImageDescriptor(ASTAccessVisibility.PROTECTED); break; default: imageDescriptor = CElementImageProvider.getFieldImageDescriptor(ASTAccessVisibility.PUBLIC); break; } } else if (binding instanceof IField) { imageDescriptor = CElementImageProvider.getFieldImageDescriptor(ASTAccessVisibility.PUBLIC); } else if (binding instanceof IVariable) { imageDescriptor = CElementImageProvider.getVariableImageDescriptor(); } else if (binding instanceof IEnumeration) { imageDescriptor = CElementImageProvider.getEnumerationImageDescriptor(); } else if (binding instanceof IEnumerator) { imageDescriptor = CElementImageProvider.getEnumeratorImageDescriptor(); } else if (binding instanceof ICPPNamespace) { imageDescriptor = CElementImageProvider.getNamespaceImageDescriptor(); } else if (binding instanceof ICPPFunctionTemplate) { imageDescriptor = CElementImageProvider.getFunctionImageDescriptor(); } else if (binding instanceof ICPPUsingDeclaration) { IBinding[] delegates = ((ICPPUsingDeclaration) binding).getDelegates(); if (delegates.length > 0) return getImageDescriptor(delegates[0]); } return imageDescriptor; } /** * Returns the matches for the type and name argument, ordered by match quality. * * @param expectedType the qualified type of the parameter we are trying to match * @param paramName the name of the parameter (used to find similarly named matches) * @param pos the position * @param suggestions the suggestions or <code>null</code> * @param isLastParameter <code>true</code> iff this proposal is for the last parameter of a method * @return returns the name of the best match, or <code>null</code> if no match found */ public ICompletionProposal[] parameterProposals(IType expectedType, String paramName, Position pos, List<IBinding> suggestions, boolean isLastParameter, IASTTranslationUnit ast) throws CModelException { List<Variable> typeMatches = new ArrayList<>(evaluateVisibleMatches(expectedType, suggestions, ast)); orderMatches(typeMatches, paramName); ICompletionProposal[] ret = new ICompletionProposal[typeMatches.size()]; int i = 0; int replacementLength = 0; for (Variable v : typeMatches) { if (i == 0) { fAlreadyMatchedNames.add(v.name); replacementLength = v.name.length(); } String displayString = v.name; final char[] triggers; if (isLastParameter) { triggers = v.triggerChars; } else { triggers = new char[v.triggerChars.length + 1]; System.arraycopy(v.triggerChars, 0, triggers, 0, v.triggerChars.length); triggers[triggers.length - 1] = ','; } ret[i++] = new PositionBasedCompletionProposal(v.name, pos, replacementLength, getImage(v.descriptor), displayString, null, null, triggers); } return ret; } private static class MatchComparator implements Comparator<Variable> { @Override public int compare(Variable one, Variable two) { return two.totalScore - one.totalScore; } } /** * Determines the best match of all possible type matches. The input into this method is all possible * completions that match the type of the argument. The purpose of this method is to choose among them * based on the following simple rules: * * 1) Local Variables > Instance/Class Variables > Inherited Instance/Class Variables * * 2) A longer case insensitive substring match will prevail * * 3) Variables that have not been used already during this completion will prevail over those that have * already been used (this avoids the same String/int/char from being passed in for multiple arguments) * * 4) A better source position score will prevail (the declaration point of the variable, or * "how close to the point of completion?" * * @param typeMatches * the list of type matches * @param paramName * the parameter name */ private static void orderMatches(List<Variable> typeMatches, String paramName) { if (typeMatches != null) { calculateVariablesScores(paramName, typeMatches); Collections.sort(typeMatches, new MatchComparator()); } } /** * Set the replacement scores of the variables with respect to the parameter name. */ private static void calculateVariablesScores(String parameterName, List<Variable> variables) { for (Variable v : variables) { v.totalScore = score(v, parameterName); } } /** * The four order criteria as described below - put already used into bit 10, all others into bits * 0-9, 11-20, 21-30; 31 is sign - always 0 * * @param v the variable * @param parameterName the name of the parameter to be replaced. * @return the score for <code>v</code> */ private static int score(Variable v, String parameterName) { int variableScore = 100 - v.variableType.getPriority(); // since these are increasing with distance int subStringScore = getContainedString(v.name, parameterName).length(); // Substring scores under 60% are not considered. // This prevents marginal matches like a - ba and false - isBool that will // destroy the sort order. int shorter = Math.min(v.name.length(), parameterName.length()); if (subStringScore < 0.6 * shorter) subStringScore = 0; int positionScore = v.positionScore; int matchedScore = v.alreadyMatched ? 0 : 1; int score = variableScore << 21 | subStringScore << 11 | matchedScore << 10 | positionScore; return score; } /** * Returns the shorter string if it is a substring of the longer string, or an empty * string otherwise. * @param first the first string * @param second the second string */ private static String getContainedString(String first, String second) { // Now only considering the case where shorter string is part of longer string. // TODO: Use a more efficient technique to get the common string (i.e. suffix tree). String shorterStr = first.length() < second.length() ? first : second; String longerStr = first == shorterStr ? second : first; if (longerStr.contains(shorterStr)) { return shorterStr; } else { return ""; //$NON-NLS-1$ } } private static Image getImage(ImageDescriptor descriptor) { return descriptor == null ? null : CUIPlugin.getImageDescriptorRegistry().get(descriptor); } }