content: /*******************************************************************************
   * Copyright (c) 2000, 2011 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.wst.jsdt.internal.ui.text.java;
  
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.NullProgressMonitor;
  import org.eclipse.wst.jsdt.core.CompletionProposal;
  import org.eclipse.wst.jsdt.core.IFunction;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.IMember;
  import org.eclipse.wst.jsdt.core.IType;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.Signature;
  import org.eclipse.wst.jsdt.core.search.IJavaScriptSearchScope;
  import org.eclipse.wst.jsdt.core.search.SearchEngine;
  import org.eclipse.wst.jsdt.core.search.SearchMatch;
  import org.eclipse.wst.jsdt.core.search.SearchParticipant;
  import org.eclipse.wst.jsdt.core.search.SearchPattern;
  import org.eclipse.wst.jsdt.core.search.SearchRequestor;
  import org.eclipse.wst.jsdt.internal.core.DefaultWorkingCopyOwner;
  import org.eclipse.wst.jsdt.internal.core.search.matching.MethodPattern;
  import org.eclipse.wst.jsdt.internal.corext.template.java.SignatureUtil;
  import org.eclipse.wst.jsdt.internal.ui.Logger;
  
  
  /**
   * Proposal info that computes the javadoc lazily when it is queried.
   *
   * 
   */
  public final class MethodProposalInfo extends MemberProposalInfo {
  
  	/**
  	 * Fallback in case we can't match a generic method. The fall back is only based
  	 * on method name and number of parameters.
  	 */
  	private IFunction fFallbackMatch;
  
  	/**
  	 * Creates a new proposal info.
  	 *
  	 * @param project the java project to reference when resolving types
  	 * @param proposal the proposal to generate information for
  	 */
  	public MethodProposalInfo(IJavaScriptProject project, CompletionProposal proposal) {
  		super(project, proposal);
  	}
  
  	/**
  	 * Resolves the member described by the receiver and returns it if found.
  	 * Returns <code>null</code> if no corresponding member can be found.
  	 *
  	 * @return the resolved member or <code>null</code> if none is found
  	 * @throws JavaScriptModelException if accessing the java model fails
  	 */
  	protected IMember resolveMember() throws JavaScriptModelException {
  		//get the type name
  		char[] typeNameChars = fProposal.getDeclarationTypeName();
  		String declaringTypeName = null;
  		if(typeNameChars != null) {
  			declaringTypeName = String.valueOf(typeNameChars);
  		}
  		
  		/* try using the signature if type name not set
  		 * NOTE: old way of doing things, should be removed at some point
  		 */
  		if(declaringTypeName == null) {
  			char[] declarationSignature= fProposal.getDeclarationSignature();
  			if(declarationSignature != null) {
  				declaringTypeName = SignatureUtil.stripSignatureToFQN(String.valueOf(declarationSignature));
  			}
  		}
  		
  		IFunction func = null;
  		if (declaringTypeName!=null) {
  			String functionName = String.valueOf(fProposal.getName());
  			
  			//get the parameter type names
  			String[] paramTypeNameStrings = null;
  			char[][] paramTypeNameChars = this.fProposal.getParameterTypeNames();
  			if(paramTypeNameChars != null) {
  				paramTypeNameStrings = new String[paramTypeNameChars.length];
  				for(int i = 0; i < paramTypeNameChars.length; ++i) {
  					paramTypeNameStrings[i] = paramTypeNameChars[i] != null ? String.valueOf(paramTypeNameChars[i]) : null;
  				}
  			} else {
  				char[] signature = fProposal.getSignature();
  				if(signature != null && signature.length > 0) {
  					paramTypeNameStrings = Signature.getParameterTypes(String.valueOf(fProposal.getSignature()));
  				} else {
  					paramTypeNameStrings = new String[0];
  				}
  			}
  			
  			//search all the possible types until a match is found
  			IType[] types = fJavaProject.findTypes(declaringTypeName);
  			if(types != null && types.length >0) {
  				for(int i = 0; i < types.length && func == null; ++i) {
  					IType type = types[i];
  					if (type != null) {
  						boolean isConstructor = fProposal.isConstructor();
  						try {
  							func = findMethod(functionName, paramTypeNameStrings, isConstructor, type);
  						} catch(JavaScriptModelException e) {
  							//ignore, could not find method
  						}
  					}
  				}
  			} else {
  				//search the index for a match
  				MethodPattern methodPattern = new MethodPattern(true, false,
  							functionName.toCharArray(),
  							new char[][] {declaringTypeName.toCharArray()},
  							SearchPattern.R_EXACT_MATCH);
  				
  				SearchEngine searchEngine = new SearchEngine(DefaultWorkingCopyOwner.PRIMARY);
  				IJavaScriptSearchScope scope = SearchEngine.createJavaSearchScope(new IJavaScriptElement[] {this.fJavaProject});
  				final List matches = new ArrayList();
  				try {
  					searchEngine.search(methodPattern,
  							new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()},
  							scope,
  							new SearchRequestor() {
  								public void acceptSearchMatch(SearchMatch match) throws CoreException {
  									if(match.getElement() instanceof IFunction) {
  										matches.add(match.getElement());
  									}
  								}
  							},
  							new NullProgressMonitor());  //using a NPM here maybe a bad idea, but nothing better to do right now
  				}
  				catch (CoreException e) {
  					Logger.logException("Failed index search for function: " + functionName, e); //$NON-NLS-1$
  				}
  				
  				// just use the first match found
  				if(!matches.isEmpty()) {
  					func = (IFunction)matches.get(0);
  				}
  			}
  		}
  		
  		return func;
  	}
  
  	/* adapted from JavaModelUtil */
  
  	/**
  	 * Finds a method in a type. This searches for a method with the same name
  	 * and signature. Parameter types are only compared by the simple name, no
  	 * resolving for the fully qualified type name is done. Constructors are
  	 * only compared by parameters, not the name.
  	 *
  	 * @param name The name of the method to find
  	 * @param paramTypes The type signatures of the parameters e.g.
  	 *        <code>{"QString;","I"}</code>
  	 * @param isConstructor If the method is a constructor
  	 * @return The first found method or <code>null</code>, if nothing found
  	 */
  	private IFunction findMethod(String name, String[] paramTypes, boolean isConstructor, IType type) throws JavaScriptModelException {
  		Map typeVariables= computeTypeVariables(type);
  		return findMethod(name, paramTypes, isConstructor, type.getFunctions(), typeVariables);
  	}
  
  	/**
  	 * The type and method signatures received in
  	 * <code>CompletionProposals</code> of type <code>FUNCTION_REF</code>
  	 * contain concrete type bounds. When comparing parameters of the signature
  	 * with an <code>IFunction</code>, we have to make sure that we match the
  	 * case where the formal method declaration uses a type variable which in
  	 * the signature is already substituted with a concrete type (bound).
  	 * <p>
  	 * This method creates a map from type variable names to type signatures
  	 * based on the position they appear in the type declaration. The type
  	 * signatures are filtered through
  	 * {@link SignatureUtil#getLowerBound(char[])}.
  	 * </p>
  	 *
  	 * @param type the type to get the variables from
  	 * @return a map from type variables to concrete type signatures
  	 * @throws JavaScriptModelException if accessing the java model fails
  	 */
  	private Map computeTypeVariables(IType type) throws JavaScriptModelException {
  		Map map= new HashMap();
  		char[] declarationSignature= fProposal.getDeclarationSignature();
  		if (declarationSignature == null) // array methods don't contain a declaration signature
  			return map;
  
  		return map;
  	}
  
  	/**
  	 * Finds a method by name. This searches for a method with a name and
  	 * signature. Parameter types are only compared by the simple name, no
  	 * resolving for the fully qualified type name is done. Constructors are
  	 * only compared by parameters, not the name.
  	 *
  	 * @param name The name of the method to find
  	 * @param paramTypes The type signatures of the parameters e.g.
  	 *        <code>{"QString;","I"}</code>
  	 * @param isConstructor If the method is a constructor
  	 * @param methods The methods to search in
  	 * @param typeVariables a map from type variables to concretely used types
  	 * @return The found method or <code>null</code>, if nothing found
  	 */
  	private IFunction findMethod(String name, String[] paramTypes, boolean isConstructor, IFunction[] methods, Map typeVariables) throws JavaScriptModelException {
  		for (int i= methods.length - 1; i >= 0; i--) {
  			if (isSameMethodSignature(name, paramTypes, isConstructor, methods[i], typeVariables)) {
  				return methods[i];
  			}
  		}
  		return fFallbackMatch;
  	}
  
  	/**
  	 * Tests if a method equals to the given signature. Parameter types are only
  	 * compared by the simple name, no resolving for the fully qualified type
  	 * name is done. Constructors are only compared by parameters, not the name.
  	 *
  	 * @param name Name of the method
  	 * @param paramTypes The type signatures of the parameters e.g.
  	 *        <code>{"QString;","I"}</code>
  	 * @param isConstructor Specifies if the method is a constructor
  	 * @param method the method to be compared with this info's method
  	 * @param typeVariables a map from type variables to types
  	 * @return Returns <code>true</code> if the method has the given name and
  	 *         parameter types and constructor state.
  	 */
  	private boolean isSameMethodSignature(String name, String[] paramTypes, boolean isConstructor, IFunction method, Map typeVariables) throws JavaScriptModelException {
  		if (isConstructor || name.equals(method.getElementName())) {
  			if (isConstructor == method.isConstructor()) {
  				String[] otherParams= method.getParameterTypes(); // types may be type variables
  				if (paramTypes.length == otherParams.length) {
  					fFallbackMatch= method;
  					String signature= method.getSignature();
  					String[] otherParamsFromSignature= Signature.getParameterTypes(signature); // types are resolved / upper-bounded
  					// no need to check method type variables since these are
  					// not yet bound when proposing a method
  					for (int i= 0; i < paramTypes.length; i++) {
  						String ourParamName= computeSimpleTypeName(paramTypes[i], typeVariables);
  						String otherParamName1= computeSimpleTypeName(otherParams[i], typeVariables);
  						String otherParamName2= computeSimpleTypeName(otherParamsFromSignature[i], typeVariables);
  						
  						if (!ourParamName.equals(otherParamName1) && !ourParamName.equals(otherParamName2)) {
  							return false;
  						}
  					}
  					return true;
  				}
  			}
  		}
  		return false;
  	}
  
  	/**
  	 * Returns the simple erased name for a given type signature, possibly replacing type variables.
  	 * 
  	 * @param signature the type signature
  	 * @param typeVariables the Map&lt;SimpleName, VariableName>
  	 * @return the simple erased name for signature
  	 */
  	private String computeSimpleTypeName(String signature, Map typeVariables) {
  		String simpleName = ""; //$NON-NLS-1$
  		if(signature != null && signature.length() > 0) {
  			// method equality uses erased types
  			String erasure=signature;
  			erasure= erasure.replaceAll("/", ".");  //$NON-NLS-1$//$NON-NLS-2$
  			simpleName= Signature.getSimpleName(Signature.toString(erasure));
  			char[] typeVar= (char[]) typeVariables.get(simpleName);
  			if (typeVar != null) {
  				simpleName= String.valueOf(Signature.getSignatureSimpleName(typeVar));
  			}
  		}
  		return simpleName;
  	}
  }
  