| /******************************************************************************* |
| * Copyright (c) 2000, 2007 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.jdt.internal.ui.text.java; |
| |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.runtime.Assert; |
| |
| import org.eclipse.swt.graphics.Image; |
| |
| import org.eclipse.jface.resource.ImageDescriptor; |
| |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jface.text.contentassist.ICompletionProposal; |
| |
| import org.eclipse.jdt.core.CompletionProposal; |
| import org.eclipse.jdt.core.CompletionRequestor; |
| import org.eclipse.jdt.core.Flags; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.ITypeHierarchy; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.Signature; |
| |
| import org.eclipse.jdt.internal.corext.util.SuperTypeHierarchyCache; |
| |
| import org.eclipse.jdt.ui.JavaElementImageDescriptor; |
| |
| import org.eclipse.jdt.internal.ui.JavaPlugin; |
| import org.eclipse.jdt.internal.ui.JavaPluginImages; |
| import org.eclipse.jdt.internal.ui.text.template.contentassist.PositionBasedCompletionProposal; |
| import org.eclipse.jdt.internal.ui.util.StringMatcher; |
| import org.eclipse.jdt.internal.ui.viewsupport.ImageDescriptorRegistry; |
| import org.eclipse.jdt.internal.ui.viewsupport.JavaElementImageProvider; |
| |
| /** |
| * This class triggers a code-completion that will track all local ane member variables for later |
| * use as a parameter guessing proposal. |
| * |
| * @author Andrew McCullough |
| */ |
| public class ParameterGuesser { |
| |
| final class Variable { |
| |
| /** |
| * Variable type. Used to choose the best guess based on scope (Local beats instance beats inherited) |
| */ |
| public static final int LOCAL= 0; |
| public static final int FIELD= 1; |
| public static final int METHOD= 1; |
| public static final int INHERITED_FIELD= 3; |
| public static final int INHERITED_METHOD= 3; |
| |
| public final String typePackage; |
| public final String typeName; |
| public final String name; |
| public final int variableType; |
| public final int positionScore; |
| public boolean alreadyMatched; |
| public char[] triggerChars; |
| public ImageDescriptor descriptor; |
| public boolean isAutoboxingMatch; |
| private String fFQN; |
| private boolean fFQNResolved= false; |
| private IType fType; |
| private boolean fTypeResolved= false; |
| |
| /** |
| * Creates a variable. |
| */ |
| public Variable(String typePackage, String typeName, String name, int variableType, int positionScore, char[] triggers, ImageDescriptor descriptor) { |
| if (typePackage == null) |
| typePackage= ""; //$NON-NLS-1$ |
| if (typeName == null) |
| typeName= ""; //$NON-NLS-1$ |
| this.typePackage= typePackage; |
| this.typeName= typeName; |
| this.name= name; |
| this.variableType= variableType; |
| this.positionScore= positionScore; |
| triggerChars= triggers; |
| this.descriptor= descriptor; |
| } |
| |
| /* |
| * @see Object#toString() |
| */ |
| public String toString() { |
| |
| StringBuffer buffer= new StringBuffer(); |
| |
| if (typePackage.length() != 0) { |
| buffer.append(typePackage); |
| buffer.append('.'); |
| } |
| |
| buffer.append(typeName); |
| buffer.append(' '); |
| buffer.append(name); |
| buffer.append(" ("); //$NON-NLS-1$ |
| buffer.append(variableType); |
| buffer.append(')'); |
| |
| return buffer.toString(); |
| } |
| |
| String getFQN() { |
| if (!fFQNResolved) { |
| fFQNResolved= true; |
| fFQN= computeFQN(typePackage, typeName); |
| } |
| return fFQN; |
| } |
| |
| private String computeFQN(String pkg, String type) { |
| if (pkg.length() != 0) { |
| return pkg + '.' + type; |
| } |
| return type; |
| } |
| |
| |
| IType getType(IJavaProject project) throws JavaModelException { |
| if (!fTypeResolved) { |
| fTypeResolved= true; |
| if (typePackage.length() > 0) |
| fType= project.findType(getFQN()); |
| } |
| return fType; |
| } |
| |
| boolean isPrimitive() { |
| return ParameterGuesser.PRIMITIVE_ASSIGNMENTS.containsKey(getFQN()); |
| } |
| |
| boolean isArrayType() { |
| // check for an exact match (fast) |
| return getFQN().endsWith("[]"); //$NON-NLS-1$ |
| } |
| |
| boolean isHierarchyAssignable(Variable rhs) throws JavaModelException { |
| IJavaProject project= fCompilationUnit.getJavaProject(); |
| IType paramType= getType(project); |
| IType varType= rhs.getType(project); |
| if (varType == null || paramType == null) |
| return false; |
| |
| ITypeHierarchy hierarchy= SuperTypeHierarchyCache.getTypeHierarchy(varType); |
| return hierarchy.contains(paramType); |
| } |
| |
| boolean isAutoBoxingAssignable(Variable rhs) { |
| // auto-unbox variable to match primitive parameter |
| if (isPrimitive()) { |
| String unboxedVariable= ParameterGuesser.getAutoUnboxedType(rhs.getFQN()); |
| return ParameterGuesser.isPrimitiveAssignable(typeName, unboxedVariable); |
| } |
| |
| // variable is primitive, auto-box to match parameter type |
| if (rhs.isPrimitive()) { |
| String unboxedType= ParameterGuesser.getAutoUnboxedType(getFQN()); |
| return ParameterGuesser.isPrimitiveAssignable(unboxedType, rhs.typeName); |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Return true if <code>rhs</code> is assignable to the receiver |
| */ |
| boolean isAssignable(Variable rhs) throws JavaModelException { |
| |
| // if there is no package specified, do the check on type name only. This will work for primitives |
| // and for local variables that cannot be resolved. |
| if (typePackage.length() == 0 || rhs.typePackage.length() == 0) { |
| |
| if (rhs.typeName.equals(typeName)) |
| return true; |
| |
| if (ParameterGuesser.isPrimitiveAssignable(typeName, rhs.typeName)) |
| return true; |
| |
| if (fAllowAutoBoxing && isAutoBoxingAssignable(rhs)) { |
| rhs.isAutoboxingMatch= true; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // if we get to here, we're doing a "fully qualified match" -- meaning including packages, no primitives |
| // and no unresolved variables. |
| |
| // if there is an exact textual match, there is no need to search type hierarchy.. this is |
| // a quick way to pick up an exact match. |
| if (rhs.getFQN().equals(getFQN())) |
| return true; |
| |
| // otherwise, we get a match/no match by searching the type hierarchy |
| return isHierarchyAssignable(rhs); |
| } |
| } |
| |
| private static final char[] NO_TRIGGERS= new char[0]; |
| private static final char[] VOID= "void".toCharArray(); //$NON-NLS-1$ |
| private static final char[] HASHCODE= "hashCode()".toCharArray(); //$NON-NLS-1$ |
| private static final char[] TOSTRING= "toString()".toCharArray(); //$NON-NLS-1$ |
| private static final char[] CLONE= "clone()".toCharArray(); //$NON-NLS-1$ |
| |
| private final class VariableCollector extends CompletionRequestor { |
| |
| /** The enclosing type name */ |
| private String fEnclosingTypeName; |
| /** The local and member variables */ |
| private List fVars; |
| |
| |
| VariableCollector() { |
| setIgnored(CompletionProposal.ANONYMOUS_CLASS_DECLARATION, true); |
| setIgnored(CompletionProposal.FIELD_REF, false); |
| setIgnored(CompletionProposal.KEYWORD, true); |
| setIgnored(CompletionProposal.LABEL_REF, true); |
| setIgnored(CompletionProposal.METHOD_DECLARATION, true); |
| setIgnored(CompletionProposal.METHOD_NAME_REFERENCE, true); |
| setIgnored(CompletionProposal.METHOD_REF, false); |
| setIgnored(CompletionProposal.PACKAGE_REF, true); |
| setIgnored(CompletionProposal.POTENTIAL_METHOD_DECLARATION, true); |
| setIgnored(CompletionProposal.VARIABLE_DECLARATION, true); |
| setIgnored(CompletionProposal.TYPE_REF, true); |
| setIgnored(CompletionProposal.ANNOTATION_ATTRIBUTE_REF, false); |
| setIgnored(CompletionProposal.LOCAL_VARIABLE_REF, false); |
| } |
| |
| public List collect(int codeAssistOffset, ICompilationUnit compilationUnit) throws JavaModelException { |
| Assert.isTrue(codeAssistOffset >= 0); |
| Assert.isNotNull(compilationUnit); |
| |
| fVars= new ArrayList(); |
| |
| String source= compilationUnit.getSource(); |
| if (source == null) |
| return fVars; |
| |
| fEnclosingTypeName= getEnclosingTypeName(codeAssistOffset, compilationUnit); |
| |
| // find some whitespace to start our variable-finding code complete from. |
| // this allows the VariableTracker to find all available variables (no prefix to match for the code completion) |
| int completionOffset= getCompletionOffset(source, codeAssistOffset); |
| |
| compilationUnit.codeComplete(completionOffset, this); |
| |
| // add this, true, false |
| int dotPos= fEnclosingTypeName.lastIndexOf('.'); |
| String thisType; |
| String thisPkg; |
| if (dotPos != -1) { |
| thisType= fEnclosingTypeName.substring(dotPos + 1); |
| thisPkg= fEnclosingTypeName.substring(0, dotPos); |
| } else { |
| thisPkg= new String(); |
| thisType= fEnclosingTypeName; |
| } |
| addVariable(Variable.FIELD, thisPkg.toCharArray(), thisType.toCharArray(), "this".toCharArray(), new char[] {'.'}, getFieldDescriptor(Flags.AccPublic | Flags.AccFinal)); //$NON-NLS-1$ |
| addVariable(Variable.FIELD, NO_TRIGGERS, "boolean".toCharArray(), "true".toCharArray(), NO_TRIGGERS, null); //$NON-NLS-1$//$NON-NLS-2$ |
| addVariable(Variable.FIELD, NO_TRIGGERS, "boolean".toCharArray(), "false".toCharArray(), NO_TRIGGERS, null); //$NON-NLS-1$//$NON-NLS-2$ |
| |
| return fVars; |
| } |
| |
| private String getEnclosingTypeName(int codeAssistOffset, ICompilationUnit compilationUnit) throws JavaModelException { |
| |
| IJavaElement element= compilationUnit.getElementAt(codeAssistOffset); |
| if (element == null) |
| return null; |
| |
| element= element.getAncestor(IJavaElement.TYPE); |
| if (element == null) |
| return null; |
| |
| return element.getElementName(); |
| } |
| |
| /** |
| * Determine if the declaring type matches the type of the code completion invocation |
| */ |
| private final boolean isInherited(String declaringTypeName) { |
| return !declaringTypeName.equals(fEnclosingTypeName); |
| } |
| |
| private void addVariable(int varType, char[] typePackageName, char[] typeName, char[] name, char[] triggers, ImageDescriptor descriptor) { |
| fVars.add(new Variable(new String(typePackageName), new String(typeName), new String(name), varType, fVars.size(), triggers, descriptor)); |
| } |
| |
| private void acceptField(char[] declaringTypeName, char[] name, char[] typePackageName, char[] typeName, int modifiers) { |
| if (!isInherited(new String(declaringTypeName))) |
| addVariable(Variable.FIELD, typePackageName, typeName, name, NO_TRIGGERS, getFieldDescriptor(modifiers)); |
| else |
| addVariable(Variable.INHERITED_FIELD, typePackageName, typeName, name, NO_TRIGGERS, getFieldDescriptor(modifiers)); |
| } |
| |
| private void acceptLocalVariable(char[] name, char[] typePackageName, char[] typeName, int modifiers) { |
| addVariable(Variable.LOCAL, typePackageName, typeName, name, NO_TRIGGERS, decorate(JavaPluginImages.DESC_OBJS_LOCAL_VARIABLE, modifiers, false)); |
| } |
| |
| private void acceptMethod(char[] declaringTypeName, char[] returnTypePackageName, char[] returnTypeName, char[] completionName, int modifiers) { |
| if (!filter(returnTypeName, completionName)) |
| addVariable(isInherited(new String(declaringTypeName)) ? Variable.INHERITED_METHOD : Variable.METHOD, returnTypePackageName, returnTypeName, completionName, NO_TRIGGERS, getMemberDescriptor(modifiers)); |
| } |
| |
| private boolean filter(char[] returnTypeName, char[] completionName) { |
| return Arrays.equals(VOID, returnTypeName) || Arrays.equals(HASHCODE, completionName) || Arrays.equals(TOSTRING, completionName) || Arrays.equals(CLONE, completionName); |
| } |
| |
| protected ImageDescriptor getMemberDescriptor(int modifiers) { |
| ImageDescriptor desc= JavaElementImageProvider.getMethodImageDescriptor(false, modifiers); |
| return decorate(desc, modifiers, false); |
| } |
| |
| protected ImageDescriptor getFieldDescriptor(int modifiers) { |
| ImageDescriptor desc= JavaElementImageProvider.getFieldImageDescriptor(false, modifiers); |
| return decorate(desc, modifiers, true); |
| } |
| |
| private ImageDescriptor decorate(ImageDescriptor descriptor, int modifiers, boolean isField) { |
| int flags= 0; |
| |
| if (Flags.isDeprecated(modifiers)) |
| flags |= JavaElementImageDescriptor.DEPRECATED; |
| |
| if (Flags.isStatic(modifiers)) |
| flags |= JavaElementImageDescriptor.STATIC; |
| |
| if (Flags.isFinal(modifiers)) |
| flags |= JavaElementImageDescriptor.FINAL; |
| |
| if (Flags.isSynchronized(modifiers)) |
| flags |= JavaElementImageDescriptor.SYNCHRONIZED; |
| |
| if (Flags.isAbstract(modifiers)) |
| flags |= JavaElementImageDescriptor.ABSTRACT; |
| |
| if (isField) { |
| if (Flags.isVolatile(modifiers)) |
| flags |= JavaElementImageDescriptor.VOLATILE; |
| |
| if (Flags.isTransient(modifiers)) |
| flags |= JavaElementImageDescriptor.TRANSIENT; |
| } |
| |
| return new JavaElementImageDescriptor(descriptor, flags, JavaElementImageProvider.SMALL_SIZE); |
| |
| } |
| |
| /* |
| * @see org.eclipse.jdt.core.CompletionRequestor#accept(org.eclipse.jdt.core.CompletionProposal) |
| */ |
| public void accept(CompletionProposal proposal) { |
| if (isIgnored(proposal.getKind())) |
| return; |
| |
| switch (proposal.getKind()) { |
| case CompletionProposal.FIELD_REF: |
| acceptField( |
| Signature.getSignatureSimpleName(proposal.getDeclarationSignature()), |
| proposal.getName(), |
| Signature.getSignatureQualifier(proposal.getSignature()), |
| Signature.getSignatureSimpleName(proposal.getSignature()), |
| proposal.getFlags()); |
| return; |
| case CompletionProposal.LOCAL_VARIABLE_REF: |
| acceptLocalVariable( |
| proposal.getCompletion(), |
| Signature.getSignatureQualifier(proposal.getSignature()), |
| Signature.getSignatureSimpleName(proposal.getSignature()), |
| proposal.getFlags()); |
| return; |
| case CompletionProposal.METHOD_REF: |
| if (Signature.getParameterCount(proposal.getSignature()) == 0) |
| acceptMethod( |
| Signature.getSignatureSimpleName(proposal.getDeclarationSignature()), |
| Signature.getSignatureQualifier(Signature.getReturnType(proposal.getSignature())), |
| Signature.getSignatureSimpleName(Signature.getReturnType(proposal.getSignature())), |
| proposal.getCompletion(), |
| proposal.getFlags()); |
| |
| } |
| |
| } |
| } |
| |
| private static final Map PRIMITIVE_ASSIGNMENTS; |
| private static final Map AUTOUNBOX; |
| |
| static { |
| HashMap primitiveAssignments= new HashMap(); |
| // put (LHS, RHS) |
| primitiveAssignments.put("boolean", Collections.singleton("boolean")); //$NON-NLS-1$ //$NON-NLS-2$ |
| primitiveAssignments.put("byte", Collections.singleton("byte")); //$NON-NLS-1$ //$NON-NLS-2$ |
| primitiveAssignments.put("short", Collections.unmodifiableSet(new HashSet(Arrays.asList(new String[] {"short", "byte"})))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| primitiveAssignments.put("char", Collections.singleton("char")); //$NON-NLS-1$ //$NON-NLS-2$ |
| primitiveAssignments.put("int", Collections.unmodifiableSet(new HashSet(Arrays.asList(new String[] {"int", "short", "char", "byte"})))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ |
| primitiveAssignments.put("long", Collections.unmodifiableSet(new HashSet(Arrays.asList(new String[] {"long", "int", "short", "char", "byte"})))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ |
| primitiveAssignments.put("float", Collections.unmodifiableSet(new HashSet(Arrays.asList(new String[] {"float", "long", "int", "short", "char", "byte"})))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ |
| primitiveAssignments.put("double", Collections.unmodifiableSet(new HashSet(Arrays.asList(new String[] {"double", "float", "long", "int", "short", "char", "byte"})))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ |
| primitiveAssignments.put("primitive number", Collections.unmodifiableSet(new HashSet(Arrays.asList(new String[] {"double", "float", "long", "int", "short", "byte"})))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ |
| PRIMITIVE_ASSIGNMENTS= Collections.unmodifiableMap(primitiveAssignments); |
| |
| HashMap autounbox= new HashMap(); |
| autounbox.put("java.lang.Boolean", "boolean"); //$NON-NLS-1$ //$NON-NLS-2$ |
| autounbox.put("java.lang.Byte", "byte"); //$NON-NLS-1$ //$NON-NLS-2$ |
| autounbox.put("java.lang.Short", "short"); //$NON-NLS-1$ //$NON-NLS-2$ |
| autounbox.put("java.lang.Character", "char"); //$NON-NLS-1$ //$NON-NLS-2$ |
| autounbox.put("java.lang.Integer", "int"); //$NON-NLS-1$ //$NON-NLS-2$ |
| autounbox.put("java.lang.Long", "long"); //$NON-NLS-1$ //$NON-NLS-2$ |
| autounbox.put("java.lang.Float", "float"); //$NON-NLS-1$ //$NON-NLS-2$ |
| autounbox.put("java.lang.Double", "double"); //$NON-NLS-1$ //$NON-NLS-2$ |
| autounbox.put("java.lang.Number", "primitive number"); // dummy for reverse assignment //$NON-NLS-1$ //$NON-NLS-2$ |
| AUTOUNBOX= Collections.unmodifiableMap(autounbox); |
| } |
| |
| private static final boolean isPrimitiveAssignable(String lhs, String rhs) { |
| Set targets= (Set) PRIMITIVE_ASSIGNMENTS.get(lhs); |
| return targets != null && targets.contains(rhs); |
| } |
| |
| private static final String getAutoUnboxedType(String type) { |
| String primitive= (String) AUTOUNBOX.get(type); |
| return primitive; |
| } |
| |
| /** The compilation unit we are computing the completion for */ |
| private final ICompilationUnit fCompilationUnit; |
| /** The code assist offset. */ |
| private final int fCodeAssistOffset; |
| /** Local and member variables of the compilation unit */ |
| private List fVariables; |
| private ImageDescriptorRegistry fRegistry= JavaPlugin.getImageDescriptorRegistry(); |
| private boolean fAllowAutoBoxing; |
| |
| /** |
| * Creates a parameter guesser for compilation unit and offset. |
| * |
| * @param codeAssistOffset the offset at which to perform code assist |
| * @param compilationUnit the compilation unit in which code assist is performed |
| */ |
| public ParameterGuesser(int codeAssistOffset, ICompilationUnit compilationUnit) { |
| Assert.isTrue(codeAssistOffset >= 0); |
| Assert.isNotNull(compilationUnit); |
| |
| fCodeAssistOffset= codeAssistOffset; |
| fCompilationUnit= compilationUnit; |
| |
| |
| IJavaProject project= fCompilationUnit.getJavaProject(); |
| String sourceVersion= project == null |
| ? JavaCore.getOption(JavaCore.COMPILER_SOURCE) |
| : project.getOption(JavaCore.COMPILER_SOURCE, true); |
| |
| fAllowAutoBoxing= JavaCore.VERSION_1_5.compareTo(sourceVersion) <= 0; |
| } |
| |
| /** |
| * Returns the offset at which code assist is performed. |
| */ |
| public int getCodeAssistOffset() { |
| return fCodeAssistOffset; |
| } |
| |
| /** |
| * Returns the compilation unit in which code assist is performed. |
| */ |
| public ICompilationUnit getCompilationUnit() { |
| return fCompilationUnit; |
| } |
| |
| /** |
| * Returns the matches for the type and name argument, ordered by match quality. |
| * |
| * @param paramPackage - the package of the parameter we are trying to match |
| * @param paramType - the qualified name of the parameter we are trying to match |
| * @param paramName - the name of the parameter (used to find similarly named matches) |
| * @param pos |
| * @param document |
| * @return returns the name of the best match, or <code>null</code> if no match found |
| * @throws JavaModelException |
| */ |
| public ICompletionProposal[] parameterProposals(String paramPackage, String paramType, String paramName, Position pos, IDocument document) throws JavaModelException { |
| |
| if (fVariables == null) { |
| VariableCollector variableCollector= new VariableCollector(); |
| fVariables= variableCollector.collect(fCodeAssistOffset, fCompilationUnit); |
| } |
| |
| Variable parameter= new Variable(paramPackage, paramType, paramName, Variable.LOCAL, 0, null, null); |
| |
| List typeMatches= findProposalsMatchingType(fVariables, parameter); |
| orderMatches(typeMatches, paramName); |
| |
| ICompletionProposal[] ret= new ICompletionProposal[typeMatches.size()]; |
| int i= 0; int replacementLength= 0; |
| for (Iterator it= typeMatches.iterator(); it.hasNext();) { |
| Variable v= (Variable)it.next(); |
| if (i == 0) { |
| v.alreadyMatched= true; |
| replacementLength= v.name.length(); |
| } |
| |
| final char[] triggers= new char[v.triggerChars.length + 1]; |
| System.arraycopy(v.triggerChars, 0, triggers, 0, v.triggerChars.length); |
| String displayString= v.isAutoboxingMatch ? v.name : v.name; |
| triggers[triggers.length - 1]= ';'; |
| ICompletionProposal proposal= new PositionBasedCompletionProposal(v.name, pos, replacementLength, getImage(v.descriptor), displayString, null, null) { |
| public char[] getTriggerCharacters() { |
| return triggers; |
| } |
| }; |
| ret[i++]= proposal; |
| } |
| |
| return ret; |
| } |
| |
| private static class MatchComparator implements Comparator { |
| |
| private String fParamName; |
| |
| MatchComparator(String paramName) { |
| fParamName= paramName; |
| } |
| public int compare(Object o1, Object o2) { |
| Variable one= (Variable)o1; |
| Variable two= (Variable)o2; |
| |
| return score(two) - score(one); |
| } |
| |
| /** |
| * 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 |
| * @return the score for <code>v</code> |
| */ |
| private int score(Variable v) { |
| int variableScore= 100 - v.variableType; // since these are increasing with distance |
| int subStringScore= getLongestCommonSubstring(v.name, fParamName).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(), fParamName.length()); |
| if (subStringScore < 0.6 * shorter) |
| subStringScore= 0; |
| |
| int positionScore= v.positionScore; // since ??? |
| int matchedScore= v.alreadyMatched ? 0 : 1; |
| int autoboxingScore= v.isAutoboxingMatch ? 0 : 1; |
| |
| int score= autoboxingScore << 30 | variableScore << 21 | subStringScore << 11 | matchedScore << 10 | positionScore; |
| return score; |
| } |
| |
| } |
| |
| /** |
| * Determine 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?" |
| */ |
| private static void orderMatches(List typeMatches, String paramName) { |
| if (typeMatches != null) Collections.sort(typeMatches, new MatchComparator(paramName)); |
| } |
| |
| /** |
| * Finds a local or member variable that matched the type of the parameter |
| */ |
| private List findProposalsMatchingType(List proposals, Variable parameter) throws JavaModelException { |
| |
| if (parameter.getFQN().length() == 0) |
| return null; |
| |
| // traverse the lists in reverse order, since it is empirically true that the code |
| // completion engine returns variables in the order they are found -- and we want to find |
| // matches closest to the code completion point.. No idea if this behavior is guaranteed. |
| |
| List matches= new ArrayList(); |
| |
| for (ListIterator iterator= proposals.listIterator(proposals.size()); iterator.hasPrevious(); ) { |
| Variable variable= (Variable) iterator.previous(); |
| variable.isAutoboxingMatch= false; |
| if (parameter.isAssignable(variable)) |
| matches.add(variable); |
| } |
| |
| return matches; |
| } |
| |
| /** |
| * Returns the longest common substring of two strings. |
| */ |
| private static String getLongestCommonSubstring(String first, String second) { |
| |
| String shorter= (first.length() <= second.length()) ? first : second; |
| String longer= shorter == first ? second : first; |
| |
| int minLength= shorter.length(); |
| |
| StringBuffer pattern= new StringBuffer(shorter.length() + 2); |
| String longestCommonSubstring= ""; //$NON-NLS-1$ |
| |
| for (int i= 0; i < minLength; i++) { |
| for (int j= i + 1; j <= minLength; j++) { |
| if (j - i < longestCommonSubstring.length()) |
| continue; |
| |
| String substring= shorter.substring(i, j); |
| pattern.setLength(0); |
| pattern.append('*'); |
| pattern.append(substring); |
| pattern.append('*'); |
| |
| StringMatcher matcher= new StringMatcher(pattern.toString(), true, false); |
| if (matcher.match(longer)) |
| longestCommonSubstring= substring; |
| } |
| } |
| |
| return longestCommonSubstring; |
| } |
| |
| private Image getImage(ImageDescriptor descriptor) { |
| return (descriptor == null) ? null : fRegistry.get(descriptor); |
| } |
| |
| private static int getCompletionOffset(String source, int start) { |
| int index= start; |
| char c; |
| while (index > 0 && (c= source.charAt(index - 1)) != '{' && c != ';') |
| index--; |
| return Math.min(index + 1, source.length()); |
| } |
| |
| } |