blob: 1a65684344fdc190cccdb26c0a6dddcd43e068b3 [file] [log] [blame]
/*******************************************************************************
* 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());
}
}