/*******************************************************************************
 * Copyright (c) 2000, 2019 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.core.manipulation.dom;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.ArrayAccess;
import org.eclipse.jdt.core.dom.ArrayCreation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.AssertStatement;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConditionalExpression;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.Initializer;
import org.eclipse.jdt.core.dom.InstanceofExpression;
import org.eclipse.jdt.core.dom.LambdaExpression;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NameQualifiedType;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.PrimitiveType.Code;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.QualifiedType;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.SwitchCase;
import org.eclipse.jdt.core.dom.SwitchExpression;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeLiteral;
import org.eclipse.jdt.core.dom.TypeParameter;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.WildcardType;
import org.eclipse.jdt.core.manipulation.TypeKinds;

import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.dom.GenericVisitor;
import org.eclipse.jdt.internal.corext.dom.IASTSharedValues;
import org.eclipse.jdt.internal.corext.dom.ScopeAnalyzer;
import org.eclipse.jdt.internal.corext.dom.TypeBindingVisitor;
import org.eclipse.jdt.internal.corext.util.JdtFlags;

import org.eclipse.jdt.internal.ui.util.ASTHelper;

/**
 * Helper methods to find AST nodes or bindings.
 */
// @see JDTUIHelperClasses
// @see org.eclipse.jdt.internal.ui.text.correction.ASTResolving (subclass of this one)
public class ASTResolving {

	public static ITypeBinding guessBindingForReference(ASTNode node) {
		return Bindings.normalizeTypeBinding(getPossibleReferenceBinding(node));
	}

	private static ITypeBinding getPossibleReferenceBinding(ASTNode node) {
		ASTNode parent= node.getParent();
		switch (parent.getNodeType()) {
		case ASTNode.ASSIGNMENT:
			Assignment assignment= (Assignment) parent;
			if (node.equals(assignment.getLeftHandSide())) {
				// field write access: xx= expression
				return assignment.getRightHandSide().resolveTypeBinding();
			}
			// read access
			return assignment.getLeftHandSide().resolveTypeBinding();
		case ASTNode.INFIX_EXPRESSION:
			InfixExpression infix= (InfixExpression) parent;
			InfixExpression.Operator op= infix.getOperator();
			if (op == InfixExpression.Operator.CONDITIONAL_AND || op == InfixExpression.Operator.CONDITIONAL_OR) {
				// boolean operation
				return infix.getAST().resolveWellKnownType("boolean"); //$NON-NLS-1$
			} else if (op == InfixExpression.Operator.LEFT_SHIFT || op == InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED || op == InfixExpression.Operator.RIGHT_SHIFT_SIGNED) {
				// asymmetric operation
				return infix.getAST().resolveWellKnownType("int"); //$NON-NLS-1$
			}
			if (node.equals(infix.getLeftOperand())) {
				//	xx operation expression
				ITypeBinding rigthHandBinding= infix.getRightOperand().resolveTypeBinding();
				if (rigthHandBinding != null) {
					return rigthHandBinding;
				}
			} else {
				// expression operation xx
				ITypeBinding leftHandBinding= infix.getLeftOperand().resolveTypeBinding();
				if (leftHandBinding != null) {
					return leftHandBinding;
				}
			}
			if (op != InfixExpression.Operator.EQUALS && op != InfixExpression.Operator.NOT_EQUALS) {
				return infix.getAST().resolveWellKnownType("int"); //$NON-NLS-1$
			}
			break;
		case ASTNode.INSTANCEOF_EXPRESSION:
			InstanceofExpression instanceofExpression= (InstanceofExpression) parent;
			return instanceofExpression.getRightOperand().resolveBinding();
		case ASTNode.VARIABLE_DECLARATION_FRAGMENT:
			VariableDeclarationFragment frag= (VariableDeclarationFragment) parent;
			if (frag.getInitializer().equals(node)) {
				return frag.getName().resolveTypeBinding();
			}
			break;
		case ASTNode.SUPER_METHOD_INVOCATION:
			SuperMethodInvocation superMethodInvocation= (SuperMethodInvocation) parent;
			IMethodBinding superMethodBinding= ASTNodes.getMethodBinding(superMethodInvocation.getName());
			if (superMethodBinding != null) {
				return getParameterTypeBinding(node, superMethodInvocation.arguments(), superMethodBinding);
			}
			break;
		case ASTNode.METHOD_INVOCATION:
			MethodInvocation methodInvocation= (MethodInvocation) parent;
			IMethodBinding methodBinding= methodInvocation.resolveMethodBinding();
			if (methodBinding != null) {
				return getParameterTypeBinding(node, methodInvocation.arguments(), methodBinding);
			}
			break;
		case ASTNode.SUPER_CONSTRUCTOR_INVOCATION: {
			SuperConstructorInvocation superInvocation= (SuperConstructorInvocation) parent;
			IMethodBinding superBinding= superInvocation.resolveConstructorBinding();
			if (superBinding != null) {
				return getParameterTypeBinding(node, superInvocation.arguments(), superBinding);
			}
			break;
		}
		case ASTNode.CONSTRUCTOR_INVOCATION: {
			ConstructorInvocation constrInvocation= (ConstructorInvocation) parent;
			IMethodBinding constrBinding= constrInvocation.resolveConstructorBinding();
			if (constrBinding != null) {
				return getParameterTypeBinding(node, constrInvocation.arguments(), constrBinding);
			}
			break;
		}
		case ASTNode.CLASS_INSTANCE_CREATION: {
			ClassInstanceCreation creation= (ClassInstanceCreation) parent;
			IMethodBinding creationBinding= creation.resolveConstructorBinding();
			if (creationBinding != null) {
				return getParameterTypeBinding(node, creation.arguments(), creationBinding);
			}
			break;
		}
		case ASTNode.PARENTHESIZED_EXPRESSION:
			return guessBindingForReference(parent);
		case ASTNode.ARRAY_ACCESS:
			if (((ArrayAccess) parent).getIndex().equals(node)) {
				return parent.getAST().resolveWellKnownType("int"); //$NON-NLS-1$
			} else {
				ITypeBinding parentBinding= getPossibleReferenceBinding(parent);
				if (parentBinding == null) {
					parentBinding= parent.getAST().resolveWellKnownType("java.lang.Object"); //$NON-NLS-1$
				}
				return parentBinding.createArrayType(1);
			}
		case ASTNode.ARRAY_CREATION:
			if (((ArrayCreation) parent).dimensions().contains(node)) {
				return parent.getAST().resolveWellKnownType("int"); //$NON-NLS-1$
			}
			break;
		case ASTNode.ARRAY_INITIALIZER:
			ASTNode initializerParent= parent.getParent();
			int dim= 1;
			while (initializerParent instanceof ArrayInitializer) {
				initializerParent= initializerParent.getParent();
				dim++;
			}
			Type creationType= null;
			if (initializerParent instanceof ArrayCreation) {
				creationType= ((ArrayCreation) initializerParent).getType();
			} else if (initializerParent instanceof VariableDeclaration) {
				VariableDeclaration varDecl= (VariableDeclaration) initializerParent;
				creationType= ASTNodes.getType(varDecl);
				dim-= varDecl.getExtraDimensions();
			} else if (initializerParent instanceof MemberValuePair) {
				String name= ((MemberValuePair) initializerParent).getName().getIdentifier();
				IMethodBinding annotMember= findAnnotationMember((Annotation) initializerParent.getParent(), name);
				if (annotMember != null) {
					return getReducedDimensionBinding(annotMember.getReturnType(), dim);
				}
			}
			if (creationType instanceof ArrayType) {
				ITypeBinding creationTypeBinding= ((ArrayType) creationType).resolveBinding();
				if (creationTypeBinding != null) {
					return Bindings.getComponentType(creationTypeBinding, dim);
				}
			}
			break;
		case ASTNode.CONDITIONAL_EXPRESSION:
			ConditionalExpression expression= (ConditionalExpression) parent;

			if (node.getLocationInParent() == ConditionalExpression.EXPRESSION_PROPERTY) {
				return parent.getAST().resolveWellKnownType("boolean"); //$NON-NLS-1$
			}

			ITypeBinding parentTypeBinding= getPossibleReferenceBinding(expression);

			if (parentTypeBinding != null
					&& !parentTypeBinding.isNullType()) {
				return parentTypeBinding;
			}

			if (node.getLocationInParent() == ConditionalExpression.THEN_EXPRESSION_PROPERTY) {
				ITypeBinding elseTypeBinding= expression.getElseExpression().resolveTypeBinding();

				if (elseTypeBinding != null
						&& !elseTypeBinding.isNullType()) {
					return elseTypeBinding;
				}
			}

			if (node.getLocationInParent() == ConditionalExpression.ELSE_EXPRESSION_PROPERTY) {
				ITypeBinding thenTypeBinding= expression.getThenExpression().resolveTypeBinding();

				if (thenTypeBinding != null
						&& !thenTypeBinding.isNullType()) {
					return thenTypeBinding;
				}
			}

			return getPossibleReferenceBinding(expression);
		case ASTNode.POSTFIX_EXPRESSION:
			return parent.getAST().resolveWellKnownType("int"); //$NON-NLS-1$
		case ASTNode.PREFIX_EXPRESSION:
			if (((PrefixExpression) parent).getOperator() == PrefixExpression.Operator.NOT) {
				return parent.getAST().resolveWellKnownType("boolean"); //$NON-NLS-1$
			}
			return parent.getAST().resolveWellKnownType("int"); //$NON-NLS-1$
		case ASTNode.ENHANCED_FOR_STATEMENT:
			if (node.getLocationInParent() == EnhancedForStatement.EXPRESSION_PROPERTY) {
				EnhancedForStatement enhancedForStatement= (EnhancedForStatement) parent;
				ITypeBinding parameterTypeBinding= enhancedForStatement.getParameter().getType().resolveBinding();

				if (parameterTypeBinding != null) {
					return parameterTypeBinding.createArrayType(1);
				}
			}
			break;
		case ASTNode.IF_STATEMENT:
		case ASTNode.WHILE_STATEMENT:
		case ASTNode.DO_STATEMENT:
			if (node instanceof Expression) {
				return parent.getAST().resolveWellKnownType("boolean"); //$NON-NLS-1$
			}
			break;
		case ASTNode.SWITCH_STATEMENT:
			if (((SwitchStatement) parent).getExpression().equals(node)) {
				return parent.getAST().resolveWellKnownType("int"); //$NON-NLS-1$
			}
			break;
		case ASTNode.RETURN_STATEMENT:
			MethodDeclaration decl= findParentMethodDeclaration(parent);
			if (decl != null && !decl.isConstructor()) {
				return decl.getReturnType2().resolveBinding();
			}
			LambdaExpression lambdaExpr= findEnclosingLambdaExpression(parent);
			if (lambdaExpr != null) {
				IMethodBinding lambdaMethodBinding= lambdaExpr.resolveMethodBinding();
				if (lambdaMethodBinding != null && lambdaMethodBinding.getReturnType() != null) {
					return lambdaMethodBinding.getReturnType();
				}
			}
			break;
		case ASTNode.CAST_EXPRESSION:
			return ((CastExpression) parent).getType().resolveBinding();
		case ASTNode.THROW_STATEMENT:
		case ASTNode.CATCH_CLAUSE:
            return parent.getAST().resolveWellKnownType("java.lang.Exception"); //$NON-NLS-1$
		case ASTNode.FIELD_ACCESS:
			if (node.equals(((FieldAccess) parent).getName())) {
				return getPossibleReferenceBinding(parent);
			}
			break;
		case ASTNode.SUPER_FIELD_ACCESS:
			return getPossibleReferenceBinding(parent);
		case ASTNode.QUALIFIED_NAME:
			if (node.equals(((QualifiedName) parent).getName())) {
				return getPossibleReferenceBinding(parent);
			}
			break;
		case ASTNode.SWITCH_CASE:
			SwitchCase switchCase= (SwitchCase) parent;
			if (node.equals(switchCase.getExpression()) || (ASTHelper.isSwitchCaseExpressionsSupportedInAST(switchCase.getAST()) && switchCase.expressions().contains(node))) {
				ASTNode caseParent= switchCase.getParent();
				if (caseParent instanceof SwitchStatement) {
					return ((SwitchStatement) caseParent).getExpression().resolveTypeBinding();
				}
				if (caseParent instanceof SwitchExpression) {
					return ((SwitchExpression) caseParent).getExpression().resolveTypeBinding();
				}
			}
			break;
		case ASTNode.ASSERT_STATEMENT:
			if (node.getLocationInParent() == AssertStatement.EXPRESSION_PROPERTY) {
				return parent.getAST().resolveWellKnownType("boolean"); //$NON-NLS-1$
			}
			return parent.getAST().resolveWellKnownType("java.lang.String"); //$NON-NLS-1$
		case ASTNode.SINGLE_MEMBER_ANNOTATION: {
			IMethodBinding annotMember= findAnnotationMember((Annotation) parent, "value"); //$NON-NLS-1$
			if (annotMember != null) {
				return annotMember.getReturnType();
			}
			break;
		}
		case ASTNode.MEMBER_VALUE_PAIR: {
			String name= ((MemberValuePair) parent).getName().getIdentifier();
			IMethodBinding annotMember= findAnnotationMember((Annotation) parent.getParent(), name);
			if (annotMember != null) {
				return annotMember.getReturnType();
			}
			break;
		}
		default:
			// do nothing
		}

		return null;
	}

	public static IMethodBinding findAnnotationMember(Annotation annotation, String name) {
		ITypeBinding annotBinding= annotation.resolveTypeBinding();
		if (annotBinding != null) {
			return Bindings.findMethodInType(annotBinding, name, (String[]) null);
		}
		return null;
	}

	public static ITypeBinding getReducedDimensionBinding(ITypeBinding arrayBinding, int dimsToReduce) {
		while (dimsToReduce > 0) {
			arrayBinding= arrayBinding.getComponentType();
			dimsToReduce--;
		}
		return arrayBinding;
	}

	public static ITypeBinding getParameterTypeBinding(ASTNode node, List<Expression> args, IMethodBinding binding) {
		int index= args.indexOf(node);
		return getParameterTypeBinding(binding, index);
	}

	public static ITypeBinding getParameterTypeBinding(IMethodBinding methodBinding, int argumentIndex) {
		ITypeBinding[] paramTypes= methodBinding.getParameterTypes();
		if (methodBinding.isVarargs() && argumentIndex >= paramTypes.length - 1) {
			return paramTypes[paramTypes.length - 1].getComponentType();
		}
		if (argumentIndex >= 0 && argumentIndex < paramTypes.length) {
			return paramTypes[argumentIndex];
		}
		return null;
	}

    public static ITypeBinding guessBindingForTypeReference(ASTNode node) {
    	StructuralPropertyDescriptor locationInParent= node.getLocationInParent();
    	if (locationInParent == QualifiedName.QUALIFIER_PROPERTY) {
    		return null; // can't guess type for X.A
    	}
		if (locationInParent == SimpleType.NAME_PROPERTY ||
				locationInParent == NameQualifiedType.NAME_PROPERTY) {
    		node= node.getParent();
    	}
    	ITypeBinding binding= Bindings.normalizeTypeBinding(getPossibleTypeBinding(node));
    	if (binding != null) {
    		if (binding.isWildcardType()) {
    			return normalizeWildcardType(binding, true, node.getAST());
    		}
    	}
    	return binding;
    }

	private static ITypeBinding getPossibleTypeBinding(ASTNode node) {
		ASTNode parent= node.getParent();
		switch (parent.getNodeType()) {
			case ASTNode.ARRAY_TYPE: {
				int dim= ((ArrayType) parent).getDimensions();
				ITypeBinding parentBinding= getPossibleTypeBinding(parent);
				if (parentBinding != null && parentBinding.getDimensions() == dim) {
					return parentBinding.getElementType();
				}
				return null;
			}
			case ASTNode.PARAMETERIZED_TYPE: {
				ITypeBinding parentBinding= getPossibleTypeBinding(parent);
				if (parentBinding == null || !parentBinding.isParameterizedType()) {
					return null;
				}
				if (node.getLocationInParent() == ParameterizedType.TYPE_PROPERTY) {
					return parentBinding;
				}

				ITypeBinding[] typeArguments= parentBinding.getTypeArguments();
				List<Type> argumentNodes= ((ParameterizedType) parent).typeArguments();
				int index= argumentNodes.indexOf(node);
				if (index != -1 && typeArguments.length == argumentNodes.size()) {
					return typeArguments[index];
				}
				return null;
			}
			case ASTNode.WILDCARD_TYPE: {
				ITypeBinding parentBinding= getPossibleTypeBinding(parent);
				if (parentBinding == null || !parentBinding.isWildcardType()) {
					return null;
				}
				WildcardType wildcardType= (WildcardType) parent;
				if (parentBinding.isUpperbound() == wildcardType.isUpperBound()) {
					return parentBinding.getBound();
				}
				return null;
			}
			case ASTNode.QUALIFIED_TYPE: {
				ITypeBinding parentBinding= getPossibleTypeBinding(parent);
				if (parentBinding == null || !parentBinding.isMember()) {
					return null;
				}
				if (node.getLocationInParent() == QualifiedType.QUALIFIER_PROPERTY) {
					return parentBinding.getDeclaringClass();
				}
				return parentBinding;
			}
			case ASTNode.NAME_QUALIFIED_TYPE: {
				ITypeBinding parentBinding= getPossibleTypeBinding(parent);
				if (parentBinding == null || !parentBinding.isMember()) {
					return null;
				}
				if (node.getLocationInParent() == NameQualifiedType.QUALIFIER_PROPERTY) {
					return parentBinding.getDeclaringClass();
				}
				return parentBinding;
			}
			case ASTNode.VARIABLE_DECLARATION_STATEMENT:
				return guessVariableType(((VariableDeclarationStatement) parent).fragments());
			case ASTNode.FIELD_DECLARATION:
				return guessVariableType(((FieldDeclaration) parent).fragments());
			case ASTNode.VARIABLE_DECLARATION_EXPRESSION:
				return guessVariableType(((VariableDeclarationExpression) parent).fragments());
			case ASTNode.SINGLE_VARIABLE_DECLARATION:
				SingleVariableDeclaration varDecl= (SingleVariableDeclaration) parent;
				if (varDecl.getInitializer() != null) {
					return Bindings.normalizeTypeBinding(varDecl.getInitializer().resolveTypeBinding());
				}
				break;
			case ASTNode.ARRAY_CREATION:
				ArrayCreation creation= (ArrayCreation) parent;
				if (creation.getInitializer() != null) {
					return creation.getInitializer().resolveTypeBinding();
				}
				return getPossibleReferenceBinding(parent);
			case ASTNode.TYPE_LITERAL:
				return ((TypeLiteral) parent).getType().resolveBinding();
			case ASTNode.CLASS_INSTANCE_CREATION:
				return getPossibleReferenceBinding(parent);
			case ASTNode.CAST_EXPRESSION:
				return getPossibleReferenceBinding(parent);
			case ASTNode.TAG_ELEMENT:
				TagElement tagElement= (TagElement) parent;
				if (TagElement.TAG_THROWS.equals(tagElement.getTagName()) || TagElement.TAG_EXCEPTION.equals(tagElement.getTagName())) {
					ASTNode methNode= tagElement.getParent().getParent();
					if (methNode instanceof MethodDeclaration) {
						List<Type> thrownExceptions= ((MethodDeclaration) methNode).thrownExceptionTypes();
						if (thrownExceptions.size() == 1) {
							return thrownExceptions.get(0).resolveBinding();
						}
					}
				}
				break;
		}
		return null;
	}

	public static ITypeBinding guessVariableType(List<VariableDeclarationFragment> fragments) {
		for (VariableDeclarationFragment frag : fragments) {
			if (frag.getInitializer() != null) {
				return Bindings.normalizeTypeBinding(frag.getInitializer().resolveTypeBinding());
			}
		}
		return null;
	}

   	/**
   	 * Finds all type bindings that contain a method of a given signature
   	 * @param searchRoot the ast node to start the search from
   	 * @param selector the method name
   	 * @param arguments the method arguments
   	 * @param context the context in which the method would be called
   	 * @return returns all types known in the AST that have a method with a given name
   	 */
	public static ITypeBinding[] getQualifierGuess(ASTNode searchRoot, final String selector, List<Expression> arguments, final IBinding context) {
		final int nArgs= arguments.size();
		final ArrayList<ITypeBinding> result= new ArrayList<>();

		// test if selector is a object method
		ITypeBinding binding= searchRoot.getAST().resolveWellKnownType("java.lang.Object"); //$NON-NLS-1$
		for (IMethodBinding meth : binding.getDeclaredMethods()) {
			if (meth.getName().equals(selector) && meth.getParameterTypes().length == nArgs) {
				return new ITypeBinding[] { binding };
			}
		}

		visitAllBindings(searchRoot, new TypeBindingVisitor() {
			private HashSet<String> fVisitedBindings= new HashSet<>(100);

			@Override
			public boolean visit(ITypeBinding node) {
				node= Bindings.normalizeTypeBinding(node);
				if (node == null) {
					return true;
				}

				if (!fVisitedBindings.add(node.getKey())) {
					return true;
				}
				if (node.isGenericType()) {
					return true; // only look at  parameterized types
				}
				if (context != null && !isUseableTypeInContext(node, context, false)) {
					return true;
				}

				for (IMethodBinding meth : node.getDeclaredMethods()) {
					if (meth.getName().equals(selector) && meth.getParameterTypes().length == nArgs) {
						result.add(node);
					}
				}
				return true;
			}
		});
		return result.toArray(new ITypeBinding[result.size()]);
	}

	public static void visitAllBindings(ASTNode astRoot, TypeBindingVisitor visitor) {
		try {
			astRoot.accept(new AllBindingsVisitor(visitor));
		} catch (AllBindingsVisitor.VisitCancelledException e) {
			// visit cancelled
		}
	}

	private static class AllBindingsVisitor extends GenericVisitor {
		private final TypeBindingVisitor fVisitor;

		private static class VisitCancelledException extends RuntimeException {
			private static final long serialVersionUID= 1L;
		}
		public AllBindingsVisitor(TypeBindingVisitor visitor) {
			super(true);
			fVisitor= visitor;
		}
		@Override
		public boolean visit(SimpleName node) {
			ITypeBinding binding= node.resolveTypeBinding();
			if (binding != null) {
				boolean res= fVisitor.visit(binding);
				if (res) {
					res= Bindings.visitHierarchy(binding, fVisitor);
				}
				if (!res) {
					throw new VisitCancelledException();
				}
			}
			return false;
		}
	}


	public static IBinding getParentMethodOrTypeBinding(ASTNode node) {
		do {
			if (node instanceof MethodDeclaration) {
				return ((MethodDeclaration) node).resolveBinding();
			} else if (node instanceof AbstractTypeDeclaration) {
				return ((AbstractTypeDeclaration) node).resolveBinding();
			} else if (node instanceof AnonymousClassDeclaration) {
				return ((AnonymousClassDeclaration) node).resolveBinding();
			}
			node= node.getParent();
		} while (node != null);

		return null;
	}

	public static BodyDeclaration findParentBodyDeclaration(ASTNode node) {
		while ((node != null) && (!(node instanceof BodyDeclaration))) {
			node= node.getParent();
		}
		return (BodyDeclaration) node;
	}

	public static BodyDeclaration findParentBodyDeclaration(ASTNode node, boolean treatModifiersOutside) {
		StructuralPropertyDescriptor lastLocation= null;

		while (node != null) {
			if (node instanceof BodyDeclaration) {
				BodyDeclaration decl= (BodyDeclaration) node;
				if (!treatModifiersOutside || lastLocation != decl.getModifiersProperty()) {
					return decl;
				}
				treatModifiersOutside= false;
			}
			lastLocation= node.getLocationInParent();
			node= node.getParent();
		}
		return null;
	}


	public static CompilationUnit findParentCompilationUnit(ASTNode node) {
		return (CompilationUnit) findAncestor(node, ASTNode.COMPILATION_UNIT);
	}

	/**
	 * Finds the ancestor type of <code>node</code> (includes <code>node</code> in the search).
	 *
	 * @param node the node to start the search from, can be <code>null</code>
	 * @param treatModifiersOutside if set, modifiers are not part of their type, but of the type's
	 *            parent
	 * @return returns the ancestor type of <code>node</code> (AbstractTypeDeclaration or
	 *         AnonymousTypeDeclaration) if any (including <code>node</code>), <code>null</code>
	 *         otherwise
	 */
	public static ASTNode findParentType(ASTNode node, boolean treatModifiersOutside) {
		StructuralPropertyDescriptor lastLocation= null;

		while (node != null) {
			if (node instanceof AbstractTypeDeclaration) {
				AbstractTypeDeclaration decl= (AbstractTypeDeclaration) node;
				if (!treatModifiersOutside || lastLocation != decl.getModifiersProperty()) {
					return decl;
				}
			} else if (node instanceof AnonymousClassDeclaration) {
				return node;
			}
			lastLocation= node.getLocationInParent();
			node= node.getParent();
		}
		return null;
	}

	/**
	 * Finds the ancestor type of <code>node</code> (includes <code>node</code> in the search).
	 *
	 * @param node the node to start the search from, can be <code>null</code>
	 * @return returns the ancestor type of <code>node</code> (AbstractTypeDeclaration or
	 *         AnonymousTypeDeclaration) if any (including <code>node</code>), <code>null</code>
	 *         otherwise
	 */
	public static ASTNode findParentType(ASTNode node) {
		return findParentType(node, false);
	}

	/**
	 * The node's enclosing method declaration or <code>null</code> if
	 * the node is not inside a method and is not a method declaration itself.
	 *
	 * @param node a node
	 * @return the enclosing method declaration or <code>null</code>
	 */
	public static MethodDeclaration findParentMethodDeclaration(ASTNode node) {
		while (node != null) {
			if (node instanceof MethodDeclaration) {
				return (MethodDeclaration) node;
			} else if (node instanceof BodyDeclaration || node instanceof AnonymousClassDeclaration || node instanceof LambdaExpression) {
				return null;
			}
			node= node.getParent();
		}
		return null;
	}

	/**
	 * Returns the lambda expression node which encloses the given <code>node</code>, or
	 * <code>null</code> if none.
	 *
	 * @param node the node
	 * @return the enclosing lambda expression node for the given <code>node</code>, or
	 *         <code>null</code> if none
	 *
	 * @since 3.10
	 */
	public static LambdaExpression findEnclosingLambdaExpression(ASTNode node) {
		node= node.getParent();
		while (node != null) {
			if (node instanceof LambdaExpression) {
				return (LambdaExpression) node;
			}
			if (node instanceof BodyDeclaration || node instanceof AnonymousClassDeclaration) {
				return null;
			}
			node= node.getParent();
		}
		return null;
	}

	/**
	 * Returns the closest ancestor of <code>node</code> (including <code>node</code> itself)
	 * whose type is <code>nodeType</code>, or <code>null</code> if none.
	 * <p>
	 * <b>Warning:</b> This method does not stop at any boundaries like parentheses, statements, body declarations, etc.
	 * The resulting node may be in a totally different scope than the given node.
	 * Consider using one of the other {@link ASTResolving}<code>.find(..)</code> methods instead.
	 * </p>
	 * @param node the node
	 * @param nodeType the node type constant from {@link ASTNode}
	 * @return the closest ancestor of <code>node</code> (including <code>node</code> itself)
	 *         whose type is <code>nodeType</code>, or <code>null</code> if none
	 */
	public static ASTNode findAncestor(ASTNode node, int nodeType) {
		while ((node != null) && (node.getNodeType() != nodeType)) {
			node= node.getParent();
		}
		return node;
	}

	public static Statement findParentStatement(ASTNode node) {
		while ((node != null) && (!(node instanceof Statement))) {
			node= node.getParent();
			if (node instanceof BodyDeclaration) {
				return null;
			}
		}
		return (Statement) node;
	}

	public static TryStatement findParentTryStatement(ASTNode node) {
		while ((node != null) && (!(node instanceof TryStatement))) {
			node= node.getParent();
			if (node instanceof BodyDeclaration || node instanceof LambdaExpression) {
				return null;
			}
		}
		return (TryStatement) node;
	}

	public static boolean isInsideConstructorInvocation(MethodDeclaration methodDeclaration, ASTNode node) {
		if (methodDeclaration.isConstructor()) {
			Statement statement= findParentStatement(node);
			if (statement instanceof ConstructorInvocation || statement instanceof SuperConstructorInvocation) {
				return true; // argument in a this or super call
			}
		}
		return false;
	}

	public static boolean isInsideModifiers(ASTNode node) {
		while (node != null && !(node instanceof BodyDeclaration)) {
			if (node instanceof Annotation) {
				return true;
			}
			node= node.getParent();
		}
		return false;
	}

	public static boolean isInStaticContext(ASTNode selectedNode) {
		BodyDeclaration decl= findParentBodyDeclaration(selectedNode);
		if (decl instanceof MethodDeclaration) {
			if (isInsideConstructorInvocation((MethodDeclaration) decl, selectedNode)) {
				return true;
			}
			return Modifier.isStatic(decl.getModifiers());
		} else if (decl instanceof Initializer) {
			return Modifier.isStatic(((Initializer)decl).getModifiers());
		} else if (decl instanceof FieldDeclaration) {
			return JdtFlags.isStatic(decl);
		}
		return false;
	}

	public static boolean isWriteAccess(Name selectedNode) {
		ASTNode curr= selectedNode;
		ASTNode parent= curr.getParent();
		while (parent != null) {
			switch (parent.getNodeType()) {
				case ASTNode.QUALIFIED_NAME:
					if (((QualifiedName) parent).getQualifier() == curr) {
						return false;
					}
					break;
				case ASTNode.FIELD_ACCESS:
					if (((FieldAccess) parent).getExpression() == curr) {
						return false;
					}
					break;
				case ASTNode.SUPER_FIELD_ACCESS:
					break;
				case ASTNode.ASSIGNMENT:
					return ((Assignment) parent).getLeftHandSide() == curr;
				case ASTNode.VARIABLE_DECLARATION_FRAGMENT:
				case ASTNode.SINGLE_VARIABLE_DECLARATION:
					return ((VariableDeclaration) parent).getName() == curr;
				case ASTNode.POSTFIX_EXPRESSION:
					return true;
				case ASTNode.PREFIX_EXPRESSION:
					PrefixExpression.Operator op= ((PrefixExpression) parent).getOperator();
					return op == PrefixExpression.Operator.DECREMENT || op == PrefixExpression.Operator.INCREMENT;
				default:
					return false;
			}

			curr= parent;
			parent= curr.getParent();
		}
		return false;
	}

	public static String getFullName(Name name) {
		return name.getFullyQualifiedName();
	}

	public static ICompilationUnit findCompilationUnitForBinding(ICompilationUnit cu, CompilationUnit astRoot, ITypeBinding binding) throws JavaModelException {
		if (binding == null || !binding.isFromSource() || binding.isTypeVariable() || binding.isWildcardType()) {
			return null;
		}
		ASTNode node= astRoot.findDeclaringNode(binding.getTypeDeclaration());
		if (node == null) {
			ICompilationUnit targetCU= Bindings.findCompilationUnit(binding, cu.getJavaProject());
			if (targetCU != null) {
				return targetCU;
			}
			return null;
		} else if (node instanceof AbstractTypeDeclaration || node instanceof AnonymousClassDeclaration) {
			return cu;
		}
		return null;
	}


	private static final Code[] CODE_ORDER= { PrimitiveType.CHAR, PrimitiveType.SHORT, PrimitiveType.INT, PrimitiveType.LONG, PrimitiveType.FLOAT, PrimitiveType.DOUBLE };

	public static ITypeBinding[] getNarrowingTypes(AST ast, ITypeBinding type) {
		ArrayList<ITypeBinding> res= new ArrayList<>();
		res.add(type);
		if (type.isPrimitive()) {
			Code code= PrimitiveType.toCode(type.getName());
			for (int i= 0; i < CODE_ORDER.length && code != CODE_ORDER[i]; i++) {
				String typeName= CODE_ORDER[i].toString();
				res.add(ast.resolveWellKnownType(typeName));
			}
		}
		return res.toArray(new ITypeBinding[res.size()]);
	}

	public static ITypeBinding[] getRelaxingTypes(AST ast, ITypeBinding type) {
		ArrayList<ITypeBinding> res= new ArrayList<>();
		res.add(type);
		if (type.isArray()) {
			res.add(ast.resolveWellKnownType("java.lang.Object")); //$NON-NLS-1$
			// The following two types are not available in some j2me implementations, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=288060 :
			ITypeBinding serializable= ast.resolveWellKnownType("java.io.Serializable"); //$NON-NLS-1$
			if (serializable != null)
				res.add(serializable);
			ITypeBinding cloneable= ast.resolveWellKnownType("java.lang.Cloneable"); //$NON-NLS-1$
			if (cloneable != null)
				res.add(cloneable);
		} else if (type.isPrimitive()) {
			Code code= PrimitiveType.toCode(type.getName());
			boolean found= false;
			for (Code order : CODE_ORDER) {
				if (found) {
					String typeName= order.toString();
					res.add(ast.resolveWellKnownType(typeName));
				}
				if (code == order) {
					found= true;
				}
			}
		} else {
			collectRelaxingTypes(res, type);
		}
		return res.toArray(new ITypeBinding[res.size()]);
	}

	private static void collectRelaxingTypes(Collection<ITypeBinding> res, ITypeBinding type) {
		for (ITypeBinding curr : type.getInterfaces()) {
			if (!res.contains(curr)) {
				res.add(curr);
			}
			collectRelaxingTypes(res, curr);
		}
		ITypeBinding binding= type.getSuperclass();
		if (binding != null) {
			if (!res.contains(binding)) {
				res.add(binding);
			}
			collectRelaxingTypes(res, binding);
		}
	}

	public static String[] getUsedVariableNames(ASTNode node) {
		CompilationUnit root= (CompilationUnit) node.getRoot();
		Collection<String> res= (new ScopeAnalyzer(root)).getUsedVariableNames(node.getStartPosition(), node.getLength());
		return res.toArray(new String[res.size()]);
	}

	private static boolean isVariableDefinedInContext(IBinding binding, ITypeBinding typeVariable) {
		if (binding.getKind() == IBinding.VARIABLE) {
			IVariableBinding var= (IVariableBinding) binding;
			binding= var.getDeclaringMethod();
			if (binding == null) {
				binding= var.getDeclaringClass();
			}
		}
		if (binding instanceof IMethodBinding) {
			if (binding == typeVariable.getDeclaringMethod()) {
				return true;
			}
			binding= ((IMethodBinding) binding).getDeclaringClass();
		}

		while (binding instanceof ITypeBinding) {
			if (binding == typeVariable.getDeclaringClass()) {
				return true;
			}
			if (Modifier.isStatic(binding.getModifiers())) {
				break;
			}
			binding= ((ITypeBinding) binding).getDeclaringClass();
		}
		return false;
	}

	public static boolean isUseableTypeInContext(ITypeBinding type, IBinding context, boolean noWildcards) {
		if (type.isArray()) {
			type= type.getElementType();
		}
		if (type.isAnonymous()) {
			return false;
		}
		if (type.isRawType() || type.isPrimitive()) {
			return true;
		}
		if (type.isTypeVariable()) {
			return isVariableDefinedInContext(context, type);
		}
		if (type.isGenericType()) {
			for (ITypeBinding typeParameter : type.getTypeParameters()) {
				if (!isUseableTypeInContext(typeParameter, context, noWildcards)) {
					return false;
				}
			}
			return true;
		}
		if (type.isParameterizedType()) {
			for (ITypeBinding typeArgument : type.getTypeArguments()) {
				if (!isUseableTypeInContext(typeArgument, context, noWildcards)) {
					return false;
				}
			}
			return true;
		}
		if (type.isCapture()) {
			type= type.getWildcard();
		}

		if (type.isWildcardType()) {
			if (noWildcards) {
				return false;
			}
			if (type.getBound() != null) {
				return isUseableTypeInContext(type.getBound(), context, noWildcards);
			}
		}
		return true;
	}

	/**
	 * Use this method before creating a type for a wildcard. Either to assign a wildcard to a new type or for a type to be assigned.
	 *
	 * @param wildcardType the wildcard type to normalize
	 * @param isBindingToAssign if true, then the type X for new variable x is returned (X x= s);
	 *     if false, the type of an expression x (R r= x)
	 * @param ast the current AST
	 * @return the normalized binding or null when only the 'null' binding
	 *
	 * @see Bindings#normalizeForDeclarationUse(ITypeBinding, AST)
	 */
	public static ITypeBinding normalizeWildcardType(ITypeBinding wildcardType, boolean isBindingToAssign, AST ast) {
		ITypeBinding bound= wildcardType.getBound();
		if (isBindingToAssign) {
			if (bound == null || !wildcardType.isUpperbound()) {
				ITypeBinding[] typeBounds= wildcardType.getTypeBounds();
				if (typeBounds.length > 0) {
					return typeBounds[0];
				} else {
					return wildcardType.getErasure();
				}
			}
		} else {
			if (bound == null || wildcardType.isUpperbound()) {
				return null;
			}
		}
		return bound;
	}


	public static CompilationUnit createQuickFixAST(ICompilationUnit compilationUnit, IProgressMonitor monitor) {
		ASTParser astParser= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL);
		astParser.setSource(compilationUnit);
		astParser.setResolveBindings(true);
		astParser.setStatementsRecovery(IASTSharedValues.SHARED_AST_STATEMENT_RECOVERY);
		astParser.setBindingsRecovery(IASTSharedValues.SHARED_BINDING_RECOVERY);
		return (CompilationUnit) astParser.createAST(monitor);
	}

	public static int getPossibleTypeKinds(ASTNode node, boolean is50OrHigher) {
		int kinds= internalGetPossibleTypeKinds(node);
		if (!is50OrHigher) {
			kinds &= (TypeKinds.INTERFACES | TypeKinds.CLASSES);
		}
		return kinds;
	}

	private static int internalGetPossibleTypeKinds(ASTNode node) {
		int kind= TypeKinds.ALL_TYPES;

		int mask= TypeKinds.ALL_TYPES | TypeKinds.VOIDTYPE;

		ASTNode parent= node.getParent();
		while (parent instanceof QualifiedName) {
			if (node.getLocationInParent() == QualifiedName.QUALIFIER_PROPERTY) {
				return TypeKinds.REF_TYPES;
			}
			node= parent;
			parent= parent.getParent();
			mask= TypeKinds.REF_TYPES;
		}
		while (parent instanceof Type) {
			if (parent instanceof QualifiedType) {
				if (node.getLocationInParent() == QualifiedType.QUALIFIER_PROPERTY) {
					return mask & (TypeKinds.REF_TYPES);
				}
				mask&= TypeKinds.REF_TYPES;
			} else if (parent instanceof NameQualifiedType) {
				if (node.getLocationInParent() == NameQualifiedType.QUALIFIER_PROPERTY) {
					return mask & (TypeKinds.REF_TYPES);
				}
				mask&= TypeKinds.REF_TYPES;
			} else if (parent instanceof ParameterizedType) {
				if (node.getLocationInParent() == ParameterizedType.TYPE_ARGUMENTS_PROPERTY) {
					return mask & TypeKinds.REF_TYPES_AND_VAR;
				}
				mask&= TypeKinds.CLASSES | TypeKinds.INTERFACES;
			} else if (parent instanceof WildcardType) {
				if (node.getLocationInParent() == WildcardType.BOUND_PROPERTY) {
					return mask & TypeKinds.REF_TYPES_AND_VAR;
				}
			}
			node= parent;
			parent= parent.getParent();
		}

		if (parent != null) {
			switch (parent.getNodeType()) {
				case ASTNode.TYPE_DECLARATION:
					if (node.getLocationInParent() == TypeDeclaration.SUPER_INTERFACE_TYPES_PROPERTY) {
						kind= TypeKinds.INTERFACES;
					} else if (node.getLocationInParent() == TypeDeclaration.SUPERCLASS_TYPE_PROPERTY) {
						kind= TypeKinds.CLASSES;
					} else if (node.getLocationInParent() == TypeDeclaration.PERMITS_TYPES_PROPERTY) {
						if (ASTHelper.isSealedTypeSupportedInAST(node.getAST())) {
							kind = TypeKinds.CLASSES;
							if (((TypeDeclaration) parent).isInterface()) {
								kind |= TypeKinds.INTERFACES;
							}
						}
					}
					break;
				case ASTNode.ENUM_DECLARATION:
					kind= TypeKinds.INTERFACES;
					break;
				case ASTNode.METHOD_DECLARATION:
					if (node.getLocationInParent() == MethodDeclaration.THROWN_EXCEPTION_TYPES_PROPERTY) {
						kind= TypeKinds.CLASSES;
					} else if (node.getLocationInParent() == MethodDeclaration.RETURN_TYPE2_PROPERTY) {
						kind= TypeKinds.ALL_TYPES | TypeKinds.VOIDTYPE;
					}
					break;
				case ASTNode.ANNOTATION_TYPE_MEMBER_DECLARATION:
					kind= TypeKinds.PRIMITIVETYPES | TypeKinds.ANNOTATIONS | TypeKinds.ENUMS;
					break;
				case ASTNode.INSTANCEOF_EXPRESSION:
					kind= TypeKinds.REF_TYPES;
					break;
				case ASTNode.THROW_STATEMENT:
					kind= TypeKinds.CLASSES;
					break;
				case ASTNode.CLASS_INSTANCE_CREATION:
					if (((ClassInstanceCreation) parent).getAnonymousClassDeclaration() == null) {
						kind= TypeKinds.CLASSES;
					} else {
						kind= TypeKinds.CLASSES | TypeKinds.INTERFACES;
					}
					break;
				case ASTNode.SINGLE_VARIABLE_DECLARATION:
					int superParent= parent.getParent().getNodeType();
					if (superParent == ASTNode.CATCH_CLAUSE) {
						kind= TypeKinds.CLASSES;
					} else if (superParent == ASTNode.ENHANCED_FOR_STATEMENT) {
						kind= TypeKinds.REF_TYPES;
					}
					break;
				case ASTNode.TAG_ELEMENT:
					kind= TypeKinds.REF_TYPES;
					break;
				case ASTNode.MARKER_ANNOTATION:
				case ASTNode.SINGLE_MEMBER_ANNOTATION:
				case ASTNode.NORMAL_ANNOTATION:
					kind= TypeKinds.ANNOTATIONS;
					break;
				case ASTNode.TYPE_PARAMETER:
					if (((TypeParameter) parent).typeBounds().indexOf(node) > 0) {
						kind= TypeKinds.INTERFACES;
					} else {
						kind= TypeKinds.REF_TYPES_AND_VAR;
					}
					break;
				case ASTNode.TYPE_LITERAL:
					kind= TypeKinds.REF_TYPES;
					break;
				default:
			}
		}
		return kind & mask;
	}
}
