1 /*******************************************************************************
2 * Copyright (c) 2000, 2011 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.jdt.internal.ui.text.java;
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.Comparator;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.List;
21 import org.eclipse.swt.graphics.Image;
23 import org.eclipse.jface.resource.ImageDescriptor;
25 import org.eclipse.jface.text.Position;
26 import org.eclipse.jface.text.contentassist.ICompletionProposal;
28 import org.eclipse.jdt.core.BindingKey;
29 import org.eclipse.jdt.core.Flags;
30 import org.eclipse.jdt.core.IField;
31 import org.eclipse.jdt.core.IJavaElement;
32 import org.eclipse.jdt.core.ILocalVariable;
33 import org.eclipse.jdt.core.IMethod;
34 import org.eclipse.jdt.core.IType;
35 import org.eclipse.jdt.core.JavaModelException;
36 import org.eclipse.jdt.core.Signature;
37 import org.eclipse.jdt.core.dom.PrimitiveType;
38 import org.eclipse.jdt.core.dom.PrimitiveType.Code;
40 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
42 import org.eclipse.jdt.ui.JavaElementImageDescriptor;
44 import org.eclipse.jdt.internal.ui.JavaPlugin;
45 import org.eclipse.jdt.internal.ui.JavaPluginImages;
46 import org.eclipse.jdt.internal.ui.text.template.contentassist.PositionBasedCompletionProposal;
47 import org.eclipse.jdt.internal.ui.util.StringMatcher;
48 import org.eclipse.jdt.internal.ui.viewsupport.JavaElementImageProvider;
52 * This class triggers a code-completion that will track all local and member variables for later
53 * use as a parameter guessing proposal.
55 public class ParameterGuesser {
57 final static class Variable {
60 * Variable type. Used to choose the best guess based on scope (Local beats instance beats inherited).
62 public static final int LOCAL= 0;
63 public static final int FIELD= 1;
64 public static final int INHERITED_FIELD= 2;
65 public static final int METHOD= 3;
66 public static final int INHERITED_METHOD= 4;
67 public static final int LITERALS= 5;
69 public final String qualifiedTypeName;
70 public final String name;
71 public final int variableType;
72 public final int positionScore;
74 public final boolean isAutoboxingMatch;
76 public final char[] triggerChars;
77 public final ImageDescriptor descriptor;
79 public boolean alreadyMatched;
81 public Variable(String qualifiedTypeName, String name, int variableType, boolean isAutoboxMatch, int positionScore, char[] triggerChars, ImageDescriptor descriptor) {
82 this.qualifiedTypeName= qualifiedTypeName;
84 this.variableType= variableType;
85 this.positionScore= positionScore;
86 this.triggerChars= triggerChars;
87 this.descriptor= descriptor;
88 this.isAutoboxingMatch= isAutoboxMatch;
89 this.alreadyMatched= false;
93 * @see Object#toString()
96 public String toString() {
98 StringBuffer buffer= new StringBuffer();
99 buffer.append(qualifiedTypeName);
102 buffer.append(" ("); //$NON-NLS-1$
103 buffer.append(variableType);
106 return buffer.toString();
109 public void generated_8831347088891252839(ArrayList<Variable> res, ParameterGuesser parameterguesser) {
110 if (parameterguesser.fAlreadyMatchedNames.contains(name)) {
111 alreadyMatched= true;
116 public void generated_140389272619292531(ParameterGuesser parameterguesser, final char[] triggers) {
117 System.arraycopy(triggerChars, 0, triggers, 0, triggerChars.length);
120 public int generated_5214888427673676057(MatchComparator matchcomparator) {
121 int variableScore= 100 - variableType; // since these are increasing with distance
122 int subStringScore= MatchComparator.getLongestCommonSubstring(name, matchcomparator.fParamName).length();
123 // substring scores under 60% are not considered
124 // this prevents marginal matches like a - ba and false - isBool that will
125 // destroy the sort order
126 int shorter= Math.min(name.length(), matchcomparator.fParamName.length());
127 if (subStringScore < 0.6 * shorter)
130 int positionScore= positionScore; // since ???
131 int matchedScore= alreadyMatched ? 0 : 1;
132 int autoboxingScore= isAutoboxingMatch ? 0 : 1;
134 int score= autoboxingScore << 30 | variableScore << 21 | subStringScore << 11 | matchedScore << 10 | positionScore;
139 private static final char[] NO_TRIGGERS= new char[0];
141 private final Set<String> fAlreadyMatchedNames;
142 private final IJavaElement fEnclosingElement;
145 * Creates a parameter guesser
147 * @param enclosingElement the enclosing Java element
149 public ParameterGuesser(IJavaElement enclosingElement) {
150 fEnclosingElement= enclosingElement;
151 fAlreadyMatchedNames= new HashSet<String>();
154 private List<Variable> evaluateVisibleMatches(String expectedType, IJavaElement[] suggestions) throws JavaModelException {
155 IType currentType= null;
156 if (fEnclosingElement != null) {
157 currentType= (IType) fEnclosingElement.getAncestor(IJavaElement.TYPE);
160 ArrayList<Variable> res= new ArrayList<Variable>();
161 for (int i= 0; i < suggestions.length; i++) {
162 Variable variable= createVariable(suggestions[i], currentType, expectedType, i);
163 if (variable != null) {
164 variable.generated_8831347088891252839(res, this);
169 if (currentType != null && !(fEnclosingElement instanceof IMethod && Flags.isStatic(((IMethod) fEnclosingElement).getFlags()))) {
170 String fullyQualifiedName= currentType.getFullyQualifiedName('.');
171 if (fullyQualifiedName.equals(expectedType)) {
172 ImageDescriptor desc= new JavaElementImageDescriptor(JavaPluginImages.DESC_FIELD_PUBLIC, JavaElementImageDescriptor.FINAL | JavaElementImageDescriptor.STATIC, JavaElementImageProvider.SMALL_SIZE);
173 res.add(new Variable(fullyQualifiedName, "this", Variable.LITERALS, false, res.size(), new char[] {'.'}, desc)); //$NON-NLS-1$
177 Code primitiveTypeCode= getPrimitiveTypeCode(expectedType);
178 if (primitiveTypeCode == null) {
180 res.add(new Variable(expectedType, "null", Variable.LITERALS, false, res.size(), NO_TRIGGERS, null)); //$NON-NLS-1$
182 String typeName= primitiveTypeCode.toString();
183 boolean isAutoboxing= !typeName.equals(expectedType);
184 if (primitiveTypeCode == PrimitiveType.BOOLEAN) {
185 // add 'true', 'false'
186 res.add(new Variable(typeName, "true", Variable.LITERALS, isAutoboxing, res.size(), NO_TRIGGERS, null)); //$NON-NLS-1$
187 res.add(new Variable(typeName, "false", Variable.LITERALS, isAutoboxing, res.size(), NO_TRIGGERS, null)); //$NON-NLS-1$
190 res.add(new Variable(typeName, "0", Variable.LITERALS, isAutoboxing, res.size(), NO_TRIGGERS, null)); //$NON-NLS-1$
196 public Variable createVariable(IJavaElement element, IType enclosingType, String expectedType, int positionScore) throws JavaModelException {
198 int elementType= element.getElementType();
199 String elementName= element.getElementName();
201 String typeSignature;
202 switch (elementType) {
203 case IJavaElement.FIELD: {
204 IField field= (IField) element;
205 if (field.getDeclaringType().equals(enclosingType)) {
206 variableType= Variable.FIELD;
208 variableType= Variable.INHERITED_FIELD;
210 if (field.isResolved()) {
211 typeSignature= new BindingKey(field.getKey()).toSignature();
213 typeSignature= field.getTypeSignature();
217 case IJavaElement.LOCAL_VARIABLE: {
218 ILocalVariable locVar= (ILocalVariable) element;
219 variableType= Variable.LOCAL;
220 typeSignature= locVar.getTypeSignature();
223 case IJavaElement.METHOD: {
224 IMethod method= (IMethod) element;
225 if (isMethodToSuggest(method)) {
226 if (method.getDeclaringType().equals(enclosingType)) {
227 variableType= Variable.METHOD;
229 variableType= Variable.INHERITED_METHOD;
231 if (method.isResolved()) {
232 typeSignature= Signature.getReturnType(new BindingKey(method.getKey()).toSignature());
234 typeSignature= method.getReturnType();
236 elementName= elementName + "()"; //$NON-NLS-1$
245 String type= Signature.toString(typeSignature);
247 boolean isAutoboxMatch= isPrimitiveType(expectedType) != isPrimitiveType(type);
248 return new Variable(type, elementName, variableType, isAutoboxMatch, positionScore, NO_TRIGGERS, getImageDescriptor(element));
251 private ImageDescriptor getImageDescriptor(IJavaElement elem) {
252 JavaElementImageProvider imageProvider= new JavaElementImageProvider();
253 return imageProvider.generated_470593898409950613(elem);
256 private boolean isPrimitiveType(String type) {
257 return PrimitiveType.toCode(type) != null;
260 private PrimitiveType.Code getPrimitiveTypeCode(String type) {
261 PrimitiveType.Code code= PrimitiveType.toCode(type);
265 if (fEnclosingElement != null && JavaModelUtil.is50OrHigher(fEnclosingElement.getJavaProject())) {
266 if (code == PrimitiveType.SHORT) {
267 if ("java.lang.Short".equals(type)) { //$NON-NLS-1$
270 } else if (code == PrimitiveType.INT) {
271 if ("java.lang.Integer".equals(type)) { //$NON-NLS-1$
274 } else if (code == PrimitiveType.LONG) {
275 if ("java.lang.Long".equals(type)) { //$NON-NLS-1$
278 } else if (code == PrimitiveType.FLOAT) {
279 if ("java.lang.Float".equals(type)) { //$NON-NLS-1$
282 } else if (code == PrimitiveType.DOUBLE) {
283 if ("java.lang.Double".equals(type)) { //$NON-NLS-1$
286 } else if (code == PrimitiveType.CHAR) {
287 if ("java.lang.Character".equals(type)) { //$NON-NLS-1$
290 } else if (code == PrimitiveType.BYTE) {
291 if ("java.lang.Byte".equals(type)) { //$NON-NLS-1$
299 private boolean isMethodToSuggest(IMethod method) {
301 String methodName= method.getElementName();
302 return method.getNumberOfParameters() == 0 && !Signature.SIG_VOID.equals(method.getReturnType())
303 && (methodName.startsWith("get") || methodName.startsWith("is")); //$NON-NLS-1$//$NON-NLS-2$
304 } catch (JavaModelException e) {
310 * Returns the matches for the type and name argument, ordered by match quality.
312 * @param expectedType - the qualified type of the parameter we are trying to match
313 * @param paramName - the name of the parameter (used to find similarly named matches)
314 * @param pos the position
315 * @param suggestions the suggestions or <code>null</code>
316 * @param fillBestGuess <code>true</code> if the best guess should be filled in
317 * @param isLastParameter <code>true</code> iff this proposal is for the last parameter of a method
318 * @return returns the name of the best match, or <code>null</code> if no match found
319 * @throws JavaModelException if it fails
321 public ICompletionProposal[] parameterProposals(String expectedType, String paramName, Position pos, IJavaElement[] suggestions, boolean fillBestGuess, boolean isLastParameter) throws JavaModelException {
322 List<Variable> typeMatches= evaluateVisibleMatches(expectedType, suggestions);
323 orderMatches(typeMatches, paramName);
325 boolean hasVarWithParamName= false;
326 ICompletionProposal[] ret= new ICompletionProposal[typeMatches.size()];
327 int i= 0; int replacementLength= 0;
328 for (Iterator<Variable> it= typeMatches.iterator(); it.hasNext();) {
329 Variable v= it.next();
331 fAlreadyMatchedNames.add(v.name);
332 replacementLength= v.name.length();
335 String displayString= v.name;
336 hasVarWithParamName |= displayString.equals(paramName);
338 final char[] triggers;
339 if (isLastParameter) {
340 triggers= v.triggerChars;
342 triggers= new char[v.triggerChars.length + 1];
343 v.generated_140389272619292531(this, triggers);
344 triggers[triggers.length - 1]= ',';
347 ret[i++]= new PositionBasedCompletionProposal(v.name, pos, replacementLength, getImage(v.descriptor), displayString, null, null, triggers);
349 if (!fillBestGuess && !hasVarWithParamName) {
350 // insert a proposal with the argument name
351 ICompletionProposal[] extended= new ICompletionProposal[ret.length + 1];
352 System.arraycopy(ret, 0, extended, 1, ret.length);
353 extended[0]= new PositionBasedCompletionProposal(paramName, pos, replacementLength, null, paramName, null, null, isLastParameter ? null : new char[] {','});
359 static class MatchComparator implements Comparator<Variable> {
361 private String fParamName;
363 MatchComparator(String paramName) {
364 fParamName= paramName;
366 public int compare(Variable one, Variable two) {
367 return score(two) - score(one);
371 * The four order criteria as described below - put already used into bit 10, all others
372 * into bits 0-9, 11-20, 21-30; 31 is sign - always 0
374 * @param v the variable
375 * @return the score for <code>v</code>
377 private int score(Variable v) {
378 int score= v.generated_5214888427673676057(MatchComparator.this);
385 * Determine the best match of all possible type matches. The input into this method is all
386 * possible completions that match the type of the argument. The purpose of this method is to
387 * choose among them based on the following simple rules:
389 * 1) Local Variables > Instance/Class Variables > Inherited Instance/Class Variables
391 * 2) A longer case insensitive substring match will prevail
393 * 3) Variables that have not been used already during this completion will prevail over
394 * those that have already been used (this avoids the same String/int/char from being passed
395 * in for multiple arguments)
397 * 4) A better source position score will prevail (the declaration point of the variable, or
398 * "how close to the point of completion?"
400 * @param typeMatches the list of type matches
401 * @param paramName the parameter name
403 private static void orderMatches(List<Variable> typeMatches, String paramName) {
404 if (typeMatches != null) Collections.sort(typeMatches, new MatchComparator(paramName));
408 * Returns the longest common substring of two strings.
410 * @param first the first string
411 * @param second the second string
412 * @return the longest common substring
414 private static String getLongestCommonSubstring(String first, String second) {
416 String shorter= (first.length() <= second.length()) ? first : second;
417 String longer= shorter == first ? second : first;
419 int minLength= shorter.length();
421 StringBuffer pattern= new StringBuffer(shorter.length() + 2);
422 String longestCommonSubstring= ""; //$NON-NLS-1$
424 for (int i= 0; i < minLength; i++) {
425 for (int j= i + 1; j <= minLength; j++) {
426 if (j - i < longestCommonSubstring.length())
429 String substring= shorter.substring(i, j);
430 pattern.setLength(0);
432 pattern.append(substring);
435 StringMatcher matcher= new StringMatcher(pattern.toString(), true, false);
436 if (matcher.match(longer))
437 longestCommonSubstring= substring;
441 return longestCommonSubstring;
444 private Image getImage(ImageDescriptor descriptor) {
445 return (descriptor == null) ? null : JavaPlugin.getImageDescriptorRegistry().get(descriptor);