/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.jsdt.internal.compiler.lookup;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;

import org.eclipse.wst.jsdt.core.compiler.CharOperation;
import org.eclipse.wst.jsdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.AnnotationTypeMemberDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.Argument;
import org.eclipse.wst.jsdt.internal.compiler.ast.AssertStatement;
import org.eclipse.wst.jsdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.TypeParameter;
import org.eclipse.wst.jsdt.internal.compiler.ast.TypeReference;
import org.eclipse.wst.jsdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.wst.jsdt.internal.compiler.impl.Constant;

public class SourceTypeBinding extends ReferenceBinding {
	public ReferenceBinding superclass;
	public ReferenceBinding[] superInterfaces;
	public FieldBinding[] fields;
	public MethodBinding[] methods;
	public ReferenceBinding[] memberTypes;
    public TypeVariableBinding[] typeVariables;

	public ClassScope scope;

	// Synthetics are separated into 5 categories: methods, super methods, fields, class literals, changed declaring type bindings and bridge methods
	public final static int METHOD_EMUL = 0;
	public final static int FIELD_EMUL = 1;
	public final static int CLASS_LITERAL_EMUL = 2;
	public final static int RECEIVER_TYPE_EMUL = 3;
	HashMap[] synthetics;
	char[] genericReferenceTypeSignature;
	
	
public SourceTypeBinding(char[][] compoundName, PackageBinding fPackage, ClassScope scope) {
	this.compoundName = compoundName;
	this.fPackage = fPackage;
	this.fileName = scope.referenceCompilationUnit().getFileName();
	this.modifiers = scope.referenceContext.modifiers;
	this.sourceName = scope.referenceContext.name;
	this.scope = scope;

	// expect the fields & methods to be initialized correctly later
	this.fields = NoFields;
	this.methods = NoMethods;

	computeId();
}
private void addDefaultAbstractMethod(MethodBinding abstractMethod) {
	MethodBinding defaultAbstract = new MethodBinding(
		abstractMethod.modifiers | AccDefaultAbstract,
		abstractMethod.selector,
		abstractMethod.returnType,
		abstractMethod.parameters,
		abstractMethod.thrownExceptions,
		this);

	MethodBinding[] temp = new MethodBinding[methods.length + 1];
	System.arraycopy(methods, 0, temp, 0, methods.length);
	temp[methods.length] = defaultAbstract;
	methods = temp;
}
public void addDefaultAbstractMethods() {
	if ((tagBits & KnowsDefaultAbstractMethods) != 0) return;

	tagBits |= KnowsDefaultAbstractMethods;

	if (isClass() && isAbstract()) {
		if (fPackage.environment.options.targetJDK >= ClassFileConstants.JDK1_2) return; // no longer added for post 1.2 targets

		ReferenceBinding[][] interfacesToVisit = new ReferenceBinding[5][];
		int lastPosition = 0;
		interfacesToVisit[lastPosition] = superInterfaces();

		for (int i = 0; i <= lastPosition; i++) {
			ReferenceBinding[] interfaces = interfacesToVisit[i];
			for (int j = 0, length = interfaces.length; j < length; j++) {
				ReferenceBinding superType = interfaces[j];
				if (superType.isValidBinding()) {
					MethodBinding[] superMethods = superType.methods();
					for (int m = superMethods.length; --m >= 0;) {
						MethodBinding method = superMethods[m];
						if (!implementsMethod(method))
							addDefaultAbstractMethod(method);
					}

					ReferenceBinding[] itsInterfaces = superType.superInterfaces();
					if (itsInterfaces != NoSuperInterfaces) {
						if (++lastPosition == interfacesToVisit.length)
							System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition);
						interfacesToVisit[lastPosition] = itsInterfaces;
					}
				}
			}
		}
	}
}
/* Add a new synthetic field for <actualOuterLocalVariable>.
*	Answer the new field or the existing field if one already existed.
*/

public FieldBinding addSyntheticField(LocalVariableBinding actualOuterLocalVariable) {
	if (synthetics == null) {
		synthetics = new HashMap[4];
	}
	if (synthetics[FIELD_EMUL] == null) {
		synthetics[FIELD_EMUL] = new HashMap(5);
	}
	
	FieldBinding synthField = (FieldBinding) synthetics[FIELD_EMUL].get(actualOuterLocalVariable);
	if (synthField == null) {
		synthField = new SyntheticFieldBinding(
			CharOperation.concat(SyntheticArgumentBinding.OuterLocalPrefix, actualOuterLocalVariable.name), 
			actualOuterLocalVariable.type, 
			AccPrivate | AccFinal | AccSynthetic, 
			this, 
			Constant.NotAConstant,
			synthetics[FIELD_EMUL].size());
		synthetics[FIELD_EMUL].put(actualOuterLocalVariable, synthField);
	}

	// ensure there is not already such a field defined by the user
	boolean needRecheck;
	int index = 1;
	do {
		needRecheck = false;
		FieldBinding existingField;
		if ((existingField = this.getField(synthField.name, true /*resolve*/)) != null) {
			TypeDeclaration typeDecl = scope.referenceContext;
			for (int i = 0, max = typeDecl.fields.length; i < max; i++) {
				FieldDeclaration fieldDecl = typeDecl.fields[i];
				if (fieldDecl.binding == existingField) {
					synthField.name = CharOperation.concat(
						SyntheticArgumentBinding.OuterLocalPrefix,
						actualOuterLocalVariable.name,
						("$" + String.valueOf(index++)).toCharArray()); //$NON-NLS-1$
					needRecheck = true;
					break;
				}
			}
		}
	} while (needRecheck);
	return synthField;
}
/* Add a new synthetic field for <enclosingType>.
*	Answer the new field or the existing field if one already existed.
*/

public FieldBinding addSyntheticField(ReferenceBinding enclosingType) {

	if (synthetics == null) {
		synthetics = new HashMap[4];
	}
	if (synthetics[FIELD_EMUL] == null) {
		synthetics[FIELD_EMUL] = new HashMap(5);
	}

	FieldBinding synthField = (FieldBinding) synthetics[FIELD_EMUL].get(enclosingType);
	if (synthField == null) {
		synthField = new SyntheticFieldBinding(
			CharOperation.concat(
				SyntheticArgumentBinding.EnclosingInstancePrefix,
				String.valueOf(enclosingType.depth()).toCharArray()),
			enclosingType,
			AccDefault | AccFinal | AccSynthetic,
			this,
			Constant.NotAConstant,
			synthetics[FIELD_EMUL].size());
		synthetics[FIELD_EMUL].put(enclosingType, synthField);
	}
	// ensure there is not already such a field defined by the user
	FieldBinding existingField;
	if ((existingField = this.getField(synthField.name, true /*resolve*/)) != null) {
		TypeDeclaration typeDecl = scope.referenceContext;
		for (int i = 0, max = typeDecl.fields.length; i < max; i++) {
			FieldDeclaration fieldDecl = typeDecl.fields[i];
			if (fieldDecl.binding == existingField) {
				scope.problemReporter().duplicateFieldInType(this, fieldDecl);
				break;
			}
		}
	}		
	return synthField;
}
/* Add a new synthetic field for a class literal access.
*	Answer the new field or the existing field if one already existed.
*/

public FieldBinding addSyntheticField(TypeBinding targetType, BlockScope blockScope) {

	if (synthetics == null) {
		synthetics = new HashMap[4];
	}
	if (synthetics[CLASS_LITERAL_EMUL] == null) {
		synthetics[CLASS_LITERAL_EMUL] = new HashMap(5);
	}

	// use a different table than FIELDS, given there might be a collision between emulation of X.this$0 and X.class.
	FieldBinding synthField = (FieldBinding) synthetics[CLASS_LITERAL_EMUL].get(targetType);
	if (synthField == null) {
		synthField = new SyntheticFieldBinding(
			("class$" + synthetics[CLASS_LITERAL_EMUL].size()).toCharArray(), //$NON-NLS-1$
			blockScope.getJavaLangClass(),
			AccDefault | AccStatic | AccSynthetic,
			this,
			Constant.NotAConstant,
			synthetics[CLASS_LITERAL_EMUL].size());
		synthetics[CLASS_LITERAL_EMUL].put(targetType, synthField);
	}
	// ensure there is not already such a field defined by the user
	FieldBinding existingField;
	if ((existingField = this.getField(synthField.name, true /*resolve*/)) != null) {
		TypeDeclaration typeDecl = blockScope.referenceType();
		for (int i = 0, max = typeDecl.fields.length; i < max; i++) {
			FieldDeclaration fieldDecl = typeDecl.fields[i];
			if (fieldDecl.binding == existingField) {
				blockScope.problemReporter().duplicateFieldInType(this, fieldDecl);
				break;
			}
		}
	}		
	return synthField;
}

/* Add a new synthetic field for the emulation of the assert statement.
*	Answer the new field or the existing field if one already existed.
*/
public FieldBinding addSyntheticField(AssertStatement assertStatement, BlockScope blockScope) {

	if (synthetics == null) {
		synthetics = new HashMap[4];
	}
	if (synthetics[FIELD_EMUL] == null) {
		synthetics[FIELD_EMUL] = new HashMap(5);
	}

	FieldBinding synthField = (FieldBinding) synthetics[FIELD_EMUL].get("assertionEmulation"); //$NON-NLS-1$
	if (synthField == null) {
		synthField = new SyntheticFieldBinding(
			"$assertionsDisabled".toCharArray(), //$NON-NLS-1$
			BooleanBinding,
			AccDefault | AccStatic | AccSynthetic | AccFinal,
			this,
			Constant.NotAConstant,
			synthetics[FIELD_EMUL].size());
		synthetics[FIELD_EMUL].put("assertionEmulation", synthField); //$NON-NLS-1$
	}
	// ensure there is not already such a field defined by the user
	// ensure there is not already such a field defined by the user
	boolean needRecheck;
	int index = 0;
	do {
		needRecheck = false;
		FieldBinding existingField;
		if ((existingField = this.getField(synthField.name, true /*resolve*/)) != null) {
			TypeDeclaration typeDecl = scope.referenceContext;
			for (int i = 0, max = typeDecl.fields.length; i < max; i++) {
				FieldDeclaration fieldDecl = typeDecl.fields[i];
				if (fieldDecl.binding == existingField) {
					synthField.name = CharOperation.concat(
						"$assertionsDisabled".toCharArray(), //$NON-NLS-1$
						("_" + String.valueOf(index++)).toCharArray()); //$NON-NLS-1$
					needRecheck = true;
					break;
				}
			}
		}
	} while (needRecheck);
	return synthField;
}

/* Add a new synthetic access method for read/write access to <targetField>.
	Answer the new method or the existing method if one already existed.
*/

public SyntheticAccessMethodBinding addSyntheticMethod(FieldBinding targetField, boolean isReadAccess) {

	if (synthetics == null) {
		synthetics = new HashMap[4];
	}
	if (synthetics[METHOD_EMUL] == null) {
		synthetics[METHOD_EMUL] = new HashMap(5);
	}

	SyntheticAccessMethodBinding accessMethod = null;
	SyntheticAccessMethodBinding[] accessors = (SyntheticAccessMethodBinding[]) synthetics[METHOD_EMUL].get(targetField);
	if (accessors == null) {
		accessMethod = new SyntheticAccessMethodBinding(targetField, isReadAccess, this);
		synthetics[METHOD_EMUL].put(targetField, accessors = new SyntheticAccessMethodBinding[2]);
		accessors[isReadAccess ? 0 : 1] = accessMethod;		
	} else {
		if ((accessMethod = accessors[isReadAccess ? 0 : 1]) == null) {
			accessMethod = new SyntheticAccessMethodBinding(targetField, isReadAccess, this);
			accessors[isReadAccess ? 0 : 1] = accessMethod;
		}
	}
	return accessMethod;
}
/* Add a new synthetic access method for access to <targetMethod>.
 * Must distinguish access method used for super access from others (need to use invokespecial bytecode)
	Answer the new method or the existing method if one already existed.
*/

public SyntheticAccessMethodBinding addSyntheticMethod(MethodBinding targetMethod, boolean isSuperAccess) {

	if (synthetics == null) {
		synthetics = new HashMap[4];
	}
	if (synthetics[METHOD_EMUL] == null) {
		synthetics[METHOD_EMUL] = new HashMap(5);
	}

	SyntheticAccessMethodBinding accessMethod = null;
	SyntheticAccessMethodBinding[] accessors = (SyntheticAccessMethodBinding[]) synthetics[METHOD_EMUL].get(targetMethod);
	if (accessors == null) {
		accessMethod = new SyntheticAccessMethodBinding(targetMethod, isSuperAccess, this);
		synthetics[METHOD_EMUL].put(targetMethod, accessors = new SyntheticAccessMethodBinding[2]);
		accessors[isSuperAccess ? 0 : 1] = accessMethod;		
	} else {
		if ((accessMethod = accessors[isSuperAccess ? 0 : 1]) == null) {
			accessMethod = new SyntheticAccessMethodBinding(targetMethod, isSuperAccess, this);
			accessors[isSuperAccess ? 0 : 1] = accessMethod;
		}
	}
	return accessMethod;
}
/* 
 * Record the fact that bridge methods need to be generated to override certain inherited methods
 */
public SyntheticAccessMethodBinding addSyntheticBridgeMethod(MethodBinding inheritedMethodToBridge, MethodBinding localTargetMethod) {
    
	if (synthetics == null) {
		synthetics = new HashMap[4];
	}
	if (synthetics[METHOD_EMUL] == null) {
		synthetics[METHOD_EMUL] = new HashMap(5);
	} else {
		// TODO (philippe) MethodBindings do not implement equals() so how do we prevent adding 2 'equal' inherited methods?
		// check to see if there is another equivalent inheritedMethod already added
		Iterator synthMethods = synthetics[METHOD_EMUL].keySet().iterator();
		while (synthMethods.hasNext()) {
			Object method = synthMethods.next();
			if (method instanceof MethodBinding)
				if (inheritedMethodToBridge.areParameterErasuresEqual((MethodBinding) method))
					if (inheritedMethodToBridge.returnType.erasure() == ((MethodBinding) method).returnType.erasure())
						return null;
		}
	}

	SyntheticAccessMethodBinding accessMethod = null;
	SyntheticAccessMethodBinding[] accessors = (SyntheticAccessMethodBinding[]) synthetics[METHOD_EMUL].get(inheritedMethodToBridge);
	if (accessors == null) {
		accessMethod = new SyntheticAccessMethodBinding(inheritedMethodToBridge, localTargetMethod);
		synthetics[METHOD_EMUL].put(inheritedMethodToBridge, accessors = new SyntheticAccessMethodBinding[2]);
		accessors[1] = accessMethod;		
	} else {
		if ((accessMethod = accessors[1]) == null) {
			accessMethod = new SyntheticAccessMethodBinding(inheritedMethodToBridge, localTargetMethod);
			accessors[1] = accessMethod;
		}
	}
	return accessMethod;
}
void faultInTypesForFieldsAndMethods() {
	fields();
	methods();

	for (int i = 0, length = memberTypes.length; i < length; i++)
		((SourceTypeBinding) memberTypes[i]).faultInTypesForFieldsAndMethods();
}

// NOTE: the type of each field of a source type is resolved when needed
public FieldBinding[] fields() {
	int failed = 0;
	try {
		for (int i = 0, length = fields.length; i < length; i++) {
			if (resolveTypeFor(fields[i]) == null) {
				fields[i] = null;
				failed++;
			}
		}
	} finally {
		if (failed > 0) {
			// ensure fields are consistent reqardless of the error
			int newSize = fields.length - failed;
			if (newSize == 0)
				return fields = NoFields;
	
			FieldBinding[] newFields = new FieldBinding[newSize];
			for (int i = 0, j = 0, length = fields.length; i < length; i++)
				if (fields[i] != null)
					newFields[j++] = fields[i];
			fields = newFields;
		}
	}
	return fields;
}
/**
 * @see org.eclipse.wst.jsdt.internal.compiler.lookup.TypeBinding#genericTypeSignature()
 */
public char[] genericTypeSignature() {
    if (this.genericReferenceTypeSignature == null) {
        if (this.typeVariables == NoTypeVariables) {
	        this.genericReferenceTypeSignature = this.signature();
        } else {
		    char[] typeSig = this.signature();
		    StringBuffer sig = new StringBuffer(10);
		    for (int i = 0; i < typeSig.length-1; i++) { // copy all but trailing semicolon
		    	sig.append(typeSig[i]);
		    }
		    sig.append('<');
		    for (int i = 0, length = this.typeVariables.length; i < length; i++) {
		        sig.append(this.typeVariables[i].genericTypeSignature());
		    }
		    sig.append(">;"); //$NON-NLS-1$
			int sigLength = sig.length();
			this.genericReferenceTypeSignature = new char[sigLength];
			sig.getChars(0, sigLength, this.genericReferenceTypeSignature, 0);		    
	    }
    }
    return this.genericReferenceTypeSignature;
}
/**
 * <param1 ... paramN>superclass superinterface1 ... superinterfaceN
 * <T:LY<TT;>;U:Ljava/lang/Object;V::Ljava/lang/Runnable;:Ljava/lang/Cloneable;:Ljava/util/Map;>Ljava/lang/Exception;Ljava/lang/Runnable;
 */
public char[] genericSignature() {
    StringBuffer sig = null;
	if (this.typeVariables != NoTypeVariables) {
	    sig = new StringBuffer(10);
	    sig.append('<');
	    for (int i = 0, length = this.typeVariables.length; i < length; i++) {
	        sig.append(this.typeVariables[i].genericSignature());
	    }
	    sig.append('>');
	} else {
	    // could still need a signature if any of supertypes is parameterized
	    noSignature: if (this.superclass == null || !this.superclass.isParameterizedType()) {
		    for (int i = 0, length = this.superInterfaces.length; i < length; i++) {
		        if (this.superInterfaces[i].isParameterizedType()) break noSignature;
		    }        
	        return null;
	    }
	    sig = new StringBuffer(10);
	}
	if (this.superclass != null) {
		sig.append(this.superclass.genericTypeSignature());
	} else {
		// interface scenario only (as Object cannot be generic) - 65953
		sig.append(scope.getJavaLangObject().genericTypeSignature());
	}
    for (int i = 0, length = this.superInterfaces.length; i < length; i++) {
        sig.append(this.superInterfaces[i].genericTypeSignature());
    }
	return sig.toString().toCharArray();
}
public MethodBinding[] getDefaultAbstractMethods() {
	int count = 0;
	for (int i = methods.length; --i >= 0;)
		if (methods[i].isDefaultAbstract())
			count++;
	if (count == 0) return NoMethods;

	MethodBinding[] result = new MethodBinding[count];
	count = 0;
	for (int i = methods.length; --i >= 0;)
		if (methods[i].isDefaultAbstract())
			result[count++] = methods[i];
	return result;
}
// NOTE: the return type, arg & exception types of each method of a source type are resolved when needed

public MethodBinding getExactConstructor(TypeBinding[] argumentTypes) {
	int argCount = argumentTypes.length;

	if ((modifiers & AccUnresolved) == 0) { // have resolved all arg types & return type of the methods
		nextMethod : for (int m = methods.length; --m >= 0;) {
			MethodBinding method = methods[m];
			if (method.selector == ConstructorDeclaration.ConstantPoolName && method.parameters.length == argCount) {
				TypeBinding[] toMatch = method.parameters;
				for (int p = 0; p < argCount; p++)
					if (toMatch[p] != argumentTypes[p])
						continue nextMethod;
				return method;
			}
		}
	} else {
		MethodBinding[] constructors = getMethods(ConstructorDeclaration.ConstantPoolName); // takes care of duplicates & default abstract methods
		nextConstructor : for (int c = constructors.length; --c >= 0;) {
			MethodBinding constructor = constructors[c];
			TypeBinding[] toMatch = constructor.parameters;
			if (toMatch.length == argCount) {
				for (int p = 0; p < argCount; p++)
					if (toMatch[p] != argumentTypes[p])
						continue nextConstructor;
				return constructor;
			}
		}
	}
	return null;
}
// NOTE: the return type, arg & exception types of each method of a source type are resolved when needed
// searches up the hierarchy as long as no potential (but not exact) match was found.

public MethodBinding getExactMethod(char[] selector, TypeBinding[] argumentTypes, CompilationUnitScope refScope) {
	if (refScope != null)
		refScope.recordTypeReference(this);

	int argCount = argumentTypes.length;
	int selectorLength = selector.length;
	boolean foundNothing = true;

	if ((modifiers & AccUnresolved) == 0) { // have resolved all arg types & return type of the methods
		nextMethod : for (int m = methods.length; --m >= 0;) {
			MethodBinding method = methods[m];
			if (method.selector.length == selectorLength && CharOperation.equals(method.selector, selector)) {
				foundNothing = false; // inner type lookups must know that a method with this name exists
				if (method.parameters.length == argCount) {
					TypeBinding[] toMatch = method.parameters;
					for (int p = 0; p < argCount; p++)
						if (toMatch[p] != argumentTypes[p])
							continue nextMethod;
					return method;
				}
			}
		}
	} else {
		MethodBinding[] matchingMethods = getMethods(selector); // takes care of duplicates & default abstract methods
		foundNothing = matchingMethods == NoMethods;
		nextMethod : for (int m = matchingMethods.length; --m >= 0;) {
			MethodBinding method = matchingMethods[m];
			TypeBinding[] toMatch = method.parameters;
			if (toMatch.length == argCount) {
				for (int p = 0; p < argCount; p++)
					if (toMatch[p] != argumentTypes[p])
						continue nextMethod;
				return method;
			}
		}
	}

	if (foundNothing) {
		if (isInterface()) {
			 if (superInterfaces.length == 1)
				return superInterfaces[0].getExactMethod(selector, argumentTypes, refScope);
		} else if (superclass != null) {
			return superclass.getExactMethod(selector, argumentTypes, refScope);
		}
	}
	return null;
}

// NOTE: the type of a field of a source type is resolved when needed
public FieldBinding getField(char[] fieldName, boolean needResolve) {
	// always resolve anyway on source types
	int fieldLength = fieldName.length;
	for (int i = 0, length = fields.length; i < length; i++) {
		FieldBinding field = fields[i];
		if (field.name.length == fieldLength && CharOperation.equals(field.name, fieldName)) {
			FieldBinding result = null;
			try {
				result = resolveTypeFor(field);
				return result;
			} finally {
				if (result == null) {
					// ensure fields are consistent reqardless of the error
					int newSize = fields.length - 1;
					if (newSize == 0) {
						fields = NoFields;
					} else {
						FieldBinding[] newFields = new FieldBinding[newSize];
						System.arraycopy(fields, 0, newFields, 0, i);
						System.arraycopy(fields, i + 1, newFields, i, newSize - i);
						fields = newFields;
					}
				}
			}
		}
	}
	return null;
}

// NOTE: the return type, arg & exception types of each method of a source type are resolved when needed
public MethodBinding[] getMethods(char[] selector) {
	int selectorLength = selector.length;
	boolean methodsAreResolved = (modifiers & AccUnresolved) == 0; // have resolved all arg types & return type of the methods
	java.util.ArrayList matchingMethods = null;
	for (int i = 0, length = methods.length; i < length; i++) {
		MethodBinding method = methods[i];
		if (method.selector.length == selectorLength && CharOperation.equals(method.selector, selector)) {
			if (!methodsAreResolved && resolveTypesFor(method) == null || method.returnType == null) {
				methods();
				return getMethods(selector); // try again since the problem methods have been removed
			}
			if (matchingMethods == null)
				matchingMethods = new java.util.ArrayList(2);
			matchingMethods.add(method);
		}
	}
	if (matchingMethods == null) return NoMethods;

	MethodBinding[] result = new MethodBinding[matchingMethods.size()];
	matchingMethods.toArray(result);
	if (!methodsAreResolved) {
		for (int i = 0, length = result.length - 1; i < length; i++) {
			MethodBinding method = result[i];
			for (int j = length; j > i; j--) {
				if (method.areParameterErasuresEqual(result[j])) {
					methods();
					return getMethods(selector); // try again since the duplicate methods have been removed
				}
			}
		}
	}
	return result;
}
/* Answer the synthetic field for <actualOuterLocalVariable>
*	or null if one does not exist.
*/

public FieldBinding getSyntheticField(LocalVariableBinding actualOuterLocalVariable) {
	
	if (synthetics == null || synthetics[FIELD_EMUL] == null) return null;
	return (FieldBinding) synthetics[FIELD_EMUL].get(actualOuterLocalVariable);
}
/* 
 * Answer the bridge method associated for an  inherited methods or null if one does not exist
 */
public SyntheticAccessMethodBinding getSyntheticBridgeMethod(MethodBinding inheritedMethodToBridge) {
    
	if (synthetics == null) return null;
	if (synthetics[METHOD_EMUL] == null) return null;
	SyntheticAccessMethodBinding[] accessors = (SyntheticAccessMethodBinding[]) synthetics[METHOD_EMUL].get(inheritedMethodToBridge);
	if (accessors == null) return null;
	return accessors[1];
}
/**
 * Returns true if a type is identical to another one,
 * or for generic types, true if compared to its raw type.
 */
public boolean isEquivalentTo(TypeBinding otherType) {
    if (this == otherType) return true;
    if (otherType == null) return false;
    if (otherType.isWildcard()) // wildcard
		return ((WildcardBinding) otherType).boundCheck(this);
    if (this.typeVariables == NoTypeVariables) return false;
    if (otherType.isParameterizedType()) {
        if ((otherType.tagBits & HasWildcard) == 0 && (!this.isMemberType() || !otherType.isMemberType())) 
        	return false; // should have been identical
        ParameterizedTypeBinding otherParamType = (ParameterizedTypeBinding) otherType;
        if (this != otherParamType.type) 
            return false;
        ReferenceBinding enclosing = enclosingType();
        if (enclosing != null && !enclosing.isEquivalentTo(otherParamType.enclosingType()))
            return false;
        int length = this.typeVariables == null ? 0 : this.typeVariables.length;
        TypeBinding[] otherArguments = otherParamType.arguments;
        int otherLength = otherArguments == null ? 0 : otherArguments.length;
        if (otherLength != length) 
            return false;
        // argument must be identical, only equivalence is allowed if wildcard other type
        for (int i = 0; i < length; i++) {
        	TypeBinding argument = this.typeVariables[i];
        	TypeBinding otherArgument = otherArguments[i];
			if (!(argument == otherArgument
					|| (otherArgument.isWildcard()) && argument.isEquivalentTo(otherArgument))) {
				return false;
			}
        }
        return true;
    } else if (otherType.isRawType())
        return otherType.erasure() == this;
	return false;
}

public boolean isGenericType() {
    return this.typeVariables != NoTypeVariables;
}

public ReferenceBinding[] memberTypes() {
	return this.memberTypes;
}
public FieldBinding getUpdatedFieldBinding(FieldBinding targetField, ReferenceBinding newDeclaringClass) {

	if (this.synthetics == null) {
		this.synthetics = new HashMap[4];
	}
	if (this.synthetics[RECEIVER_TYPE_EMUL] == null) {
		this.synthetics[RECEIVER_TYPE_EMUL] = new HashMap(5);
	}

	Hashtable fieldMap = (Hashtable) this.synthetics[RECEIVER_TYPE_EMUL].get(targetField);
	if (fieldMap == null) {
		fieldMap = new Hashtable(5);
		this.synthetics[RECEIVER_TYPE_EMUL].put(targetField, fieldMap);
	}
	FieldBinding updatedField = (FieldBinding) fieldMap.get(newDeclaringClass);
	if (updatedField == null){
		updatedField = new FieldBinding(targetField, newDeclaringClass);
		fieldMap.put(newDeclaringClass, updatedField);
	}
	return updatedField;
}

public MethodBinding getUpdatedMethodBinding(MethodBinding targetMethod, ReferenceBinding newDeclaringClass) {

	if (this.synthetics == null) {
		this.synthetics = new HashMap[4];
	}
	if (this.synthetics[RECEIVER_TYPE_EMUL] == null) {
		this.synthetics[RECEIVER_TYPE_EMUL] = new HashMap(5);
	}


	Hashtable methodMap = (Hashtable) synthetics[RECEIVER_TYPE_EMUL].get(targetMethod);
	if (methodMap == null) {
		methodMap = new Hashtable(5);
		this.synthetics[RECEIVER_TYPE_EMUL].put(targetMethod, methodMap);
	}
	MethodBinding updatedMethod = (MethodBinding) methodMap.get(newDeclaringClass);
	if (updatedMethod == null){
		updatedMethod = new MethodBinding(targetMethod, newDeclaringClass);
		methodMap.put(newDeclaringClass, updatedMethod);
	}
	return updatedMethod;
}
public boolean hasMemberTypes() {
    return this.memberTypes.length > 0;
}
// NOTE: the return type, arg & exception types of each method of a source type are resolved when needed
public MethodBinding[] methods() {
	if ((modifiers & AccUnresolved) == 0)
		return methods;

	int failed = 0;
	try {
		for (int i = 0, length = methods.length; i < length; i++) {
			if (resolveTypesFor(methods[i]) == null) {
				methods[i] = null; // unable to resolve parameters
				failed++;
			}
		}

		// find & report collision cases
		for (int i = 0, length = methods.length; i < length; i++) {
			MethodBinding method = methods[i];
			if (method != null) {
				AbstractMethodDeclaration methodDecl = null;
				for (int j = length - 1; j > i; j--) {
					MethodBinding method2 = methods[j];
					if (method2 != null && CharOperation.equals(method.selector, method2.selector)) {
						if (method.areParameterErasuresEqual(method2)) {
							if (methodDecl == null) {
								methodDecl = method.sourceMethod(); // cannot be retrieved after binding is lost
								scope.problemReporter().duplicateMethodInType(this, methodDecl);
								methodDecl.binding = null;
								methods[i] = null;
								failed++;
							}
							AbstractMethodDeclaration method2Decl = method2.sourceMethod();
							scope.problemReporter().duplicateMethodInType(this, method2Decl);
							method2Decl.binding = null;
							methods[j] = null;
							failed++;
						}
					}
				}
				if (method.returnType == null && methodDecl == null) { // forget method with invalid return type... was kept to detect possible collisions
					method.sourceMethod().binding = null;
					methods[i] = null;
					failed++;
				}
			}
		}
	} finally {
		if (failed > 0) {
			int newSize = methods.length - failed;
			if (newSize == 0) {
				methods = NoMethods;
			} else {
				MethodBinding[] newMethods = new MethodBinding[newSize];
				for (int i = 0, j = 0, length = methods.length; i < length; i++)
					if (methods[i] != null)
						newMethods[j++] = methods[i];
				methods = newMethods;
			}
		}

		// handle forward references to potential default abstract methods
		addDefaultAbstractMethods();

		modifiers ^= AccUnresolved;
	}		
	return methods;
}
private FieldBinding resolveTypeFor(FieldBinding field) {
	if ((field.modifiers & AccUnresolved) == 0)
		return field;

	FieldDeclaration[] fieldDecls = scope.referenceContext.fields;
	for (int f = 0, length = fieldDecls.length; f < length; f++) {
		if (fieldDecls[f].binding != field)
			continue;

			MethodScope initializationScope = field.isStatic() 
				? scope.referenceContext.staticInitializerScope 
				: scope.referenceContext.initializerScope;
			FieldBinding previousField = initializationScope.initializedField;
			try {
				initializationScope.initializedField = field;
				TypeBinding fieldType = fieldDecls[f].type.resolveType(initializationScope);
				field.type = fieldType;
				field.modifiers ^= AccUnresolved;
				if (fieldType == null) {
					fieldDecls[f].binding = null;
					return null;
				}
				if (fieldType == VoidBinding) {
					scope.problemReporter().variableTypeCannotBeVoid(fieldDecls[f]);
					fieldDecls[f].binding = null;
					return null;
				}
				if (fieldType.isArrayType() && ((ArrayBinding) fieldType).leafComponentType == VoidBinding) {
					scope.problemReporter().variableTypeCannotBeVoidArray(fieldDecls[f]);
					fieldDecls[f].binding = null;
					return null;
				}
				if (fieldType instanceof ReferenceBinding && (((ReferenceBinding)fieldType).modifiers & AccGenericSignature) != 0) {
					field.modifiers |= AccGenericSignature;
				}				
			} finally {
			    initializationScope.initializedField = previousField;
			}
		return field;
	}
	return null; // should never reach this point
}
private MethodBinding resolveTypesFor(MethodBinding method) {
    
	if ((method.modifiers & AccUnresolved) == 0)
		return method;

	AbstractMethodDeclaration methodDecl = method.sourceMethod();
	if (methodDecl == null) return null; // method could not be resolved in previous iteration
	
	TypeParameter[] typeParameters = methodDecl.typeParameters();
	if (typeParameters != null) methodDecl.scope.connectTypeVariables(typeParameters);
	TypeReference[] exceptionTypes = methodDecl.thrownExceptions;
	if (exceptionTypes != null) {
		int size = exceptionTypes.length;
		method.thrownExceptions = new ReferenceBinding[size];
		ReferenceBinding throwable = scope.getJavaLangThrowable();
		int count = 0;
		ReferenceBinding resolvedExceptionType;
		for (int i = 0; i < size; i++) {
			resolvedExceptionType = (ReferenceBinding) exceptionTypes[i].resolveType(methodDecl.scope);
			if (resolvedExceptionType == null) {
				continue;
			}
			if (resolvedExceptionType.isGenericType() || resolvedExceptionType.isParameterizedType()) {
				methodDecl.scope.problemReporter().invalidParameterizedExceptionType(resolvedExceptionType, exceptionTypes[i]);
				continue;
			}
			if (throwable != resolvedExceptionType && !throwable.isSuperclassOf(resolvedExceptionType)) {
				methodDecl.scope.problemReporter().cannotThrowType(this, methodDecl, exceptionTypes[i], resolvedExceptionType);
				continue;
			}
		    if ((resolvedExceptionType.modifiers & AccGenericSignature) != 0) {
				method.modifiers |= AccGenericSignature;
			}
			method.thrownExceptions[count++] = resolvedExceptionType;
		}
		if (count < size)
			System.arraycopy(method.thrownExceptions, 0, method.thrownExceptions = new ReferenceBinding[count], 0, count);
	}

	boolean foundArgProblem = false;
	Argument[] arguments = methodDecl.arguments;
	if (arguments != null) {
		int size = arguments.length;
		method.parameters = new TypeBinding[size];
		for (int i = 0; i < size; i++) {
			Argument arg = arguments[i];
			TypeBinding parameterType = arg.type.resolveType(methodDecl.scope);
			if (parameterType == null) {
				foundArgProblem = true;
			} else if (parameterType == VoidBinding) {
				methodDecl.scope.problemReporter().argumentTypeCannotBeVoid(this, methodDecl, arg);
				foundArgProblem = true;
			} else if (parameterType.isArrayType() && ((ArrayBinding) parameterType).leafComponentType == VoidBinding) {
				methodDecl.scope.problemReporter().argumentTypeCannotBeVoidArray(this, methodDecl, arg);
				foundArgProblem = true;
			} else {
			    if (parameterType instanceof ReferenceBinding && (((ReferenceBinding)parameterType).modifiers & AccGenericSignature) != 0) {
					method.modifiers |= AccGenericSignature;
				}
				method.parameters[i] = parameterType;
			}
		}
	}

	boolean foundReturnTypeProblem = false;
	if (!method.isConstructor()) {
		TypeReference returnType = methodDecl instanceof MethodDeclaration
			? ((MethodDeclaration) methodDecl).returnType
			: ((AnnotationTypeMemberDeclaration) methodDecl).returnType;
		if (returnType == null) {
			methodDecl.scope.problemReporter().missingReturnType(methodDecl);
			method.returnType = null;
			foundReturnTypeProblem = true;
		} else {
		    TypeBinding methodType = returnType.resolveType(methodDecl.scope);
			if (methodType == null) {
				foundReturnTypeProblem = true;
			} else if (methodType.isArrayType() && ((ArrayBinding) methodType).leafComponentType == VoidBinding) {
				methodDecl.scope.problemReporter().returnTypeCannotBeVoidArray(this, (MethodDeclaration) methodDecl);
				foundReturnTypeProblem = true;
			} else {
				method.returnType = methodType;
				if (methodType instanceof ReferenceBinding && (((ReferenceBinding)methodType).modifiers & AccGenericSignature) != 0) {
					method.modifiers |= AccGenericSignature;
				}
			}
		}
	}
	if (foundArgProblem) {
		methodDecl.binding = null;
		return null;
	}
	if (foundReturnTypeProblem)
		return method; // but its still unresolved with a null return type & is still connected to its method declaration

	method.modifiers ^= AccUnresolved;
	return method;
}
public final int sourceEnd() {
	return scope.referenceContext.sourceEnd;
}
public final int sourceStart() {
	return scope.referenceContext.sourceStart;
}
public ReferenceBinding superclass() {
	return superclass;
}
public ReferenceBinding[] superInterfaces() {
	return superInterfaces;
}
// TODO (philippe) could be a performance issue since some senders are building the list just to count them
public SyntheticAccessMethodBinding[] syntheticAccessMethods() {
	
	if (synthetics == null || synthetics[METHOD_EMUL] == null || synthetics[METHOD_EMUL].size() == 0) return null;

	// difficult to compute size up front because of the embedded arrays so assume there is only 1
	int index = 0;
	SyntheticAccessMethodBinding[] bindings = new SyntheticAccessMethodBinding[1];
	Iterator fieldsOrMethods = synthetics[METHOD_EMUL].keySet().iterator();
	while (fieldsOrMethods.hasNext()) {

		Object fieldOrMethod = fieldsOrMethods.next();

		if (fieldOrMethod instanceof MethodBinding) {

			SyntheticAccessMethodBinding[] methodAccessors = (SyntheticAccessMethodBinding[]) synthetics[METHOD_EMUL].get(fieldOrMethod);
			int numberOfAccessors = 0;
			if (methodAccessors[0] != null) numberOfAccessors++;
			if (methodAccessors[1] != null) numberOfAccessors++;
			if (index + numberOfAccessors > bindings.length)
				System.arraycopy(bindings, 0, (bindings = new SyntheticAccessMethodBinding[index + numberOfAccessors]), 0, index);
			if (methodAccessors[0] != null) 
				bindings[index++] = methodAccessors[0]; // super access 
			if (methodAccessors[1] != null) 
				bindings[index++] = methodAccessors[1]; // normal access or bridge

		} else {

			SyntheticAccessMethodBinding[] fieldAccessors = (SyntheticAccessMethodBinding[]) synthetics[METHOD_EMUL].get(fieldOrMethod);
			int numberOfAccessors = 0;
			if (fieldAccessors[0] != null) numberOfAccessors++;
			if (fieldAccessors[1] != null) numberOfAccessors++;
			if (index + numberOfAccessors > bindings.length)
				System.arraycopy(bindings, 0, (bindings = new SyntheticAccessMethodBinding[index + numberOfAccessors]), 0, index);
			if (fieldAccessors[0] != null) 
				bindings[index++] = fieldAccessors[0]; // read access
			if (fieldAccessors[1] != null) 
				bindings[index++] = fieldAccessors[1]; // write access
		}
	}

	// sort them in according to their own indexes
	int length;
	SyntheticAccessMethodBinding[] sortedBindings = new SyntheticAccessMethodBinding[length = bindings.length];
	for (int i = 0; i < length; i++){
		SyntheticAccessMethodBinding binding = bindings[i];
		sortedBindings[binding.index] = binding;
	}
	return sortedBindings;
}
/**
 * Answer the collection of synthetic fields to append into the classfile
 */
public FieldBinding[] syntheticFields() {
	
	if (synthetics == null) return null;

	int fieldSize = synthetics[FIELD_EMUL] == null ? 0 : synthetics[FIELD_EMUL].size();
	int literalSize = synthetics[CLASS_LITERAL_EMUL] == null ? 0 :synthetics[CLASS_LITERAL_EMUL].size();
	int totalSize = fieldSize + literalSize;
	if (totalSize == 0) return null;
	FieldBinding[] bindings = new FieldBinding[totalSize];

	// add innerclass synthetics
	if (synthetics[FIELD_EMUL] != null){
		Iterator elements = synthetics[FIELD_EMUL].values().iterator();
		for (int i = 0; i < fieldSize; i++) {
			SyntheticFieldBinding synthBinding = (SyntheticFieldBinding) elements.next();
			bindings[synthBinding.index] = synthBinding;
		}
	}
	// add class literal synthetics
	if (synthetics[CLASS_LITERAL_EMUL] != null){
		Iterator elements = synthetics[CLASS_LITERAL_EMUL].values().iterator();
		for (int i = 0; i < literalSize; i++) {
			SyntheticFieldBinding synthBinding = (SyntheticFieldBinding) elements.next();
			bindings[fieldSize+synthBinding.index] = synthBinding;
		}
	}
	return bindings;
}
public String toString() {
    StringBuffer buffer = new StringBuffer(30);
    buffer.append("(id="); //$NON-NLS-1$
    if (id == NoId) 
        buffer.append("NoId"); //$NON-NLS-1$
    else 
        buffer.append(id);
    buffer.append(")\n"); //$NON-NLS-1$
	if (isDeprecated()) buffer.append("deprecated "); //$NON-NLS-1$
	if (isPublic()) buffer.append("public "); //$NON-NLS-1$
	if (isProtected()) buffer.append("protected "); //$NON-NLS-1$
	if (isPrivate()) buffer.append("private "); //$NON-NLS-1$
	if (isAbstract() && isClass()) buffer.append("abstract "); //$NON-NLS-1$
	if (isStatic() && isNestedType()) buffer.append("static "); //$NON-NLS-1$
	if (isFinal()) buffer.append("final "); //$NON-NLS-1$

	buffer.append(isInterface() ? "interface " : "class "); //$NON-NLS-1$ //$NON-NLS-2$
	buffer.append((compoundName != null) ? CharOperation.toString(compoundName) : "UNNAMED TYPE"); //$NON-NLS-1$

	if (this.typeVariables != null && this.typeVariables != NoTypeVariables) {
		buffer.append("\n\t<"); //$NON-NLS-1$
		for (int i = 0, length = this.typeVariables.length; i < length; i++) {
			if (i  > 0)
				buffer.append(", "); //$NON-NLS-1$
			buffer.append((this.typeVariables[i] != null) ? this.typeVariables[i].toString() : "NULL TYPE VARIABLE"); //$NON-NLS-1$
		}
		buffer.append(">"); //$NON-NLS-1$
	} else {
		buffer.append("<NULL TYPE VARIABLES>"); //$NON-NLS-1$
	}
	buffer.append("\n\textends "); //$NON-NLS-1$
	buffer.append((superclass != null) ? superclass.debugName() : "NULL TYPE"); //$NON-NLS-1$

	if (superInterfaces != null) {
		if (superInterfaces != NoSuperInterfaces) {
			buffer.append("\n\timplements : "); //$NON-NLS-1$
			for (int i = 0, length = superInterfaces.length; i < length; i++) {
				if (i  > 0)
					buffer.append(", "); //$NON-NLS-1$
				buffer.append((superInterfaces[i] != null) ? superInterfaces[i].debugName() : "NULL TYPE"); //$NON-NLS-1$
			}
		}
	} else {
		buffer.append("NULL SUPERINTERFACES"); //$NON-NLS-1$
	}

	if (enclosingType() != null) {
		buffer.append("\n\tenclosing type : "); //$NON-NLS-1$
		buffer.append(enclosingType().debugName());
	}

	if (fields != null) {
		if (fields != NoFields) {
			buffer.append("\n/*   fields   */"); //$NON-NLS-1$
			for (int i = 0, length = fields.length; i < length; i++)
			    buffer.append('\n').append((fields[i] != null) ? fields[i].toString() : "NULL FIELD"); //$NON-NLS-1$ 
		}
	} else {
		buffer.append("NULL FIELDS"); //$NON-NLS-1$
	}

	if (methods != null) {
		if (methods != NoMethods) {
			buffer.append("\n/*   methods   */"); //$NON-NLS-1$
			for (int i = 0, length = methods.length; i < length; i++)
				buffer.append('\n').append((methods[i] != null) ? methods[i].toString() : "NULL METHOD"); //$NON-NLS-1$ //$NON-NLS-2$
		}
	} else {
		buffer.append("NULL METHODS"); //$NON-NLS-1$
	}

	if (memberTypes != null) {
		if (memberTypes != NoMemberTypes) {
			buffer.append("\n/*   members   */"); //$NON-NLS-1$
			for (int i = 0, length = memberTypes.length; i < length; i++)
				buffer.append('\n').append((memberTypes[i] != null) ? memberTypes[i].toString() : "NULL TYPE"); //$NON-NLS-1$ //$NON-NLS-2$
		}
	} else {
		buffer.append("NULL MEMBER TYPES"); //$NON-NLS-1$
	}

	buffer.append("\n\n"); //$NON-NLS-1$
	return buffer.toString();
}
public TypeVariableBinding[] typeVariables() {
	return this.typeVariables;
}
void verifyMethods(MethodVerifier verifier) {
	verifier.verify(this);

	for (int i = memberTypes.length; --i >= 0;)
		 ((SourceTypeBinding) memberTypes[i]).verifyMethods(verifier);
}

/* Answer the synthetic field for <targetEnclosingType>
*	or null if one does not exist.
*/

public FieldBinding getSyntheticField(ReferenceBinding targetEnclosingType, boolean onlyExactMatch) {

	if (synthetics == null || synthetics[FIELD_EMUL] == null) return null;
	FieldBinding field = (FieldBinding) synthetics[FIELD_EMUL].get(targetEnclosingType);
	if (field != null) return field;

	// type compatibility : to handle cases such as
	// class T { class M{}}
	// class S extends T { class N extends M {}} --> need to use S as a default enclosing instance for the super constructor call in N().
	if (!onlyExactMatch){
		Iterator accessFields = synthetics[FIELD_EMUL].values().iterator();
		while (accessFields.hasNext()) {
			field = (FieldBinding) accessFields.next();
			if (CharOperation.prefixEquals(SyntheticArgumentBinding.EnclosingInstancePrefix, field.name)
				&& ((ReferenceBinding) field.type).findSuperTypeErasingTo(targetEnclosingType) != null)
					return field;
		}
	}
	return null;
}
}
