/*******************************************************************************
 * 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 org.eclipse.wst.jsdt.core.compiler.CharOperation;
import org.eclipse.wst.jsdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.Clinit;
import org.eclipse.wst.jsdt.internal.compiler.ast.FieldDeclaration;
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.problem.AbortCompilation;
import org.eclipse.wst.jsdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.wst.jsdt.internal.compiler.util.HashtableOfObject;

public class ClassScope extends Scope {
	public TypeDeclaration referenceContext;
	private TypeReference superTypeReference;

	private final static char[] IncompleteHierarchy = new char[] {'h', 'a', 's', ' ', 'i', 'n', 'c', 'o', 'n', 's', 'i', 's', 't', 'e', 'n', 't', ' ', 'h', 'i', 'e', 'r', 'a', 'r', 'c', 'h', 'y'};

	public ClassScope(Scope parent, TypeDeclaration context) {
		super(CLASS_SCOPE, parent);
		this.referenceContext = context;
	}
	
	void buildAnonymousTypeBinding(SourceTypeBinding enclosingType, ReferenceBinding supertype) {
		
		LocalTypeBinding anonymousType = buildLocalType(enclosingType, enclosingType.fPackage);

		SourceTypeBinding sourceType = referenceContext.binding;
		if (supertype.isInterface()) {
			sourceType.superclass = getJavaLangObject();
			sourceType.superInterfaces = new ReferenceBinding[] { supertype };
		} else {
			sourceType.superclass = supertype;
			sourceType.superInterfaces = TypeConstants.NoSuperInterfaces;
		}
		connectMemberTypes();
		buildFieldsAndMethods();
		anonymousType.faultInTypesForFieldsAndMethods();
		sourceType.verifyMethods(environment().methodVerifier());
	}
	
	private void buildFields() {
		boolean hierarchyIsInconsistent = referenceContext.binding.isHierarchyInconsistent();
		if (referenceContext.fields == null) {
			if (hierarchyIsInconsistent) { // 72468
				referenceContext.binding.fields = new FieldBinding[1];
				referenceContext.binding.fields[0] =
					new FieldBinding(IncompleteHierarchy, VoidBinding, AccPrivate, referenceContext.binding, null);
			} else {
				referenceContext.binding.fields = NoFields;
			}
			return;
		}
		// count the number of fields vs. initializers
		FieldDeclaration[] fields = referenceContext.fields;
		int size = fields.length;
		int count = 0;
		for (int i = 0; i < size; i++)
			if (fields[i].isField())
				count++;

		if (hierarchyIsInconsistent)
			count++;
		// iterate the field declarations to create the bindings, lose all duplicates
		FieldBinding[] fieldBindings = new FieldBinding[count];
		HashtableOfObject knownFieldNames = new HashtableOfObject(count);
		boolean duplicate = false;
		count = 0;
		for (int i = 0; i < size; i++) {
			FieldDeclaration field = fields[i];
			if (!field.isField()) {
				if (referenceContext.binding.isInterface())
					problemReporter().interfaceCannotHaveInitializers(referenceContext.binding, field);
			} else {
				FieldBinding fieldBinding = new FieldBinding(field, null, field.modifiers | AccUnresolved, referenceContext.binding);
				// field's type will be resolved when needed for top level types
				checkAndSetModifiersForField(fieldBinding, field);

				if (knownFieldNames.containsKey(field.name)) {
					duplicate = true;
					FieldBinding previousBinding = (FieldBinding) knownFieldNames.get(field.name);
					if (previousBinding != null) {
						for (int f = 0; f < i; f++) {
							FieldDeclaration previousField = fields[f];
							if (previousField.binding == previousBinding) {
								problemReporter().duplicateFieldInType(referenceContext.binding, previousField);
								previousField.binding = null;
								break;
							}
						}
					}
					knownFieldNames.put(field.name, null); // ensure that the duplicate field is found & removed
					problemReporter().duplicateFieldInType(referenceContext.binding, field);
					field.binding = null;
				} else {
					knownFieldNames.put(field.name, fieldBinding);
					// remember that we have seen a field with this name
					if (fieldBinding != null)
						fieldBindings[count++] = fieldBinding;
				}
			}
		}
		// remove duplicate fields
		if (duplicate) {
			FieldBinding[] newFieldBindings = new FieldBinding[knownFieldNames.size() - 1];
			// we know we'll be removing at least 1 duplicate name
			size = count;
			count = 0;
			for (int i = 0; i < size; i++) {
				FieldBinding fieldBinding = fieldBindings[i];
				if (knownFieldNames.get(fieldBinding.name) != null)
					newFieldBindings[count++] = fieldBinding;
			}
			fieldBindings = newFieldBindings;
		}
		if (hierarchyIsInconsistent)
			fieldBindings[count++] = new FieldBinding(IncompleteHierarchy, VoidBinding, AccPrivate, referenceContext.binding, null);

		if (count != fieldBindings.length)
			System.arraycopy(fieldBindings, 0, fieldBindings = new FieldBinding[count], 0, count);
		for (int i = 0; i < count; i++)
			fieldBindings[i].id = i;
		referenceContext.binding.fields = fieldBindings;
	}
	
	void buildFieldsAndMethods() {
		buildFields();
		buildMethods();

		SourceTypeBinding sourceType = referenceContext.binding;
		if (sourceType.isMemberType() && !sourceType.isLocalType())
			 ((MemberTypeBinding) sourceType).checkSyntheticArgsAndFields();

		ReferenceBinding[] memberTypes = sourceType.memberTypes;
		for (int i = 0, length = memberTypes.length; i < length; i++)
			 ((SourceTypeBinding) memberTypes[i]).scope.buildFieldsAndMethods();
	}
	
	private LocalTypeBinding buildLocalType(
		SourceTypeBinding enclosingType,
		PackageBinding packageBinding) {
	    
		referenceContext.scope = this;
		referenceContext.staticInitializerScope = new MethodScope(this, referenceContext, true);
		referenceContext.initializerScope = new MethodScope(this, referenceContext, false);

		// build the binding or the local type
		LocalTypeBinding localType = new LocalTypeBinding(this, enclosingType, this.switchCase());
		referenceContext.binding = localType;
		checkAndSetModifiers();
		buildTypeVariables();
		
		// Look at member types
		ReferenceBinding[] memberTypeBindings = NoMemberTypes;
		if (referenceContext.memberTypes != null) {
			int size = referenceContext.memberTypes.length;
			memberTypeBindings = new ReferenceBinding[size];
			int count = 0;
			nextMember : for (int i = 0; i < size; i++) {
				TypeDeclaration memberContext = referenceContext.memberTypes[i];
				if (memberContext.isInterface()) {
					problemReporter().nestedClassCannotDeclareInterface(memberContext);
					continue nextMember;
				}
				ReferenceBinding type = localType;
				// check that the member does not conflict with an enclosing type
				do {
					if (CharOperation.equals(type.sourceName, memberContext.name)) {
						problemReporter().hidingEnclosingType(memberContext);
						continue nextMember;
					}
					type = type.enclosingType();
				} while (type != null);
				// check the member type does not conflict with another sibling member type
				for (int j = 0; j < i; j++) {
					if (CharOperation.equals(referenceContext.memberTypes[j].name, memberContext.name)) {
						problemReporter().duplicateNestedType(memberContext);
						continue nextMember;
					}
				}
				ClassScope memberScope = new ClassScope(this, referenceContext.memberTypes[i]);
				LocalTypeBinding memberBinding = memberScope.buildLocalType(localType, packageBinding);
				memberBinding.setAsMemberType();
				memberTypeBindings[count++] = memberBinding;
			}
			if (count != size)
				System.arraycopy(memberTypeBindings, 0, memberTypeBindings = new ReferenceBinding[count], 0, count);
		}
		localType.memberTypes = memberTypeBindings;
		return localType;
	}
	
	void buildLocalTypeBinding(SourceTypeBinding enclosingType) {

		LocalTypeBinding localType = buildLocalType(enclosingType, enclosingType.fPackage);
		connectTypeHierarchy();
		buildFieldsAndMethods();
		localType.faultInTypesForFieldsAndMethods();

		referenceContext.binding.verifyMethods(environment().methodVerifier());
	}
	
	private void buildMemberTypes() {
	    SourceTypeBinding sourceType = referenceContext.binding;
		ReferenceBinding[] memberTypeBindings = NoMemberTypes;
		if (referenceContext.memberTypes != null) {
			int length = referenceContext.memberTypes.length;
			memberTypeBindings = new ReferenceBinding[length];
			int count = 0;
			nextMember : for (int i = 0; i < length; i++) {
				TypeDeclaration memberContext = referenceContext.memberTypes[i];
				if (memberContext.isInterface()
					&& sourceType.isNestedType()
					&& sourceType.isClass()
					&& !sourceType.isStatic()) {
					problemReporter().nestedClassCannotDeclareInterface(memberContext);
					continue nextMember;
				}
				ReferenceBinding type = sourceType;
				// check that the member does not conflict with an enclosing type
				do {
					if (CharOperation.equals(type.sourceName, memberContext.name)) {
						problemReporter().hidingEnclosingType(memberContext);
						continue nextMember;
					}
					type = type.enclosingType();
				} while (type != null);
				// check that the member type does not conflict with another sibling member type
				for (int j = 0; j < i; j++) {
					if (CharOperation.equals(referenceContext.memberTypes[j].name, memberContext.name)) {
						problemReporter().duplicateNestedType(memberContext);
						continue nextMember;
					}
				}

				ClassScope memberScope = new ClassScope(this, memberContext);
				memberTypeBindings[count++] = memberScope.buildType(sourceType, sourceType.fPackage);
			}
			if (count != length)
				System.arraycopy(memberTypeBindings, 0, memberTypeBindings = new ReferenceBinding[count], 0, count);
		}
		sourceType.memberTypes = memberTypeBindings;
	}
	
	private void buildMethods() {
		if (referenceContext.methods == null) {
			referenceContext.binding.methods = NoMethods;
			return;
		}

		// iterate the method declarations to create the bindings
		AbstractMethodDeclaration[] methods = referenceContext.methods;
		int size = methods.length;
		int clinitIndex = -1;
		for (int i = 0; i < size; i++) {
			if (methods[i] instanceof Clinit) {
				clinitIndex = i;
				break;
			}
		}
		MethodBinding[] methodBindings = new MethodBinding[clinitIndex == -1 ? size : size - 1];

		int count = 0;
		for (int i = 0; i < size; i++) {
			if (i != clinitIndex) {
				MethodScope scope = new MethodScope(this, methods[i], false);
				MethodBinding methodBinding = scope.createMethod(methods[i]);
				if (methodBinding != null) // is null if binding could not be created
					methodBindings[count++] = methodBinding;
			}
		}
		if (count != methodBindings.length)
			System.arraycopy(methodBindings, 0, methodBindings = new MethodBinding[count], 0, count);

		referenceContext.binding.methods = methodBindings;
		referenceContext.binding.modifiers |= AccUnresolved; // until methods() is sent
	}
	
	SourceTypeBinding buildType(SourceTypeBinding enclosingType, PackageBinding packageBinding) {
		// provide the typeDeclaration with needed scopes
		referenceContext.scope = this;
		referenceContext.staticInitializerScope = new MethodScope(this, referenceContext, true);
		referenceContext.initializerScope = new MethodScope(this, referenceContext, false);

		if (enclosingType == null) {
			char[][] className = CharOperation.arrayConcat(packageBinding.compoundName, referenceContext.name);
			referenceContext.binding = new SourceTypeBinding(className, packageBinding, this);
		} else {
			char[][] className = CharOperation.deepCopy(enclosingType.compoundName);
			className[className.length - 1] =
				CharOperation.concat(className[className.length - 1], referenceContext.name, '$');
			referenceContext.binding = new MemberTypeBinding(className, this, enclosingType);
		}

		SourceTypeBinding sourceType = referenceContext.binding;
		sourceType.fPackage.addType(sourceType);
		checkAndSetModifiers();
		buildTypeVariables();
		buildMemberTypes();
		return sourceType;
	}
	
	private void buildTypeVariables() {
	    
	    SourceTypeBinding sourceType = referenceContext.binding;
		TypeParameter[] typeParameters = referenceContext.typeParameters;
		
	    // do not construct type variables if source < 1.5
		if (typeParameters == null || environment().options.sourceLevel < ClassFileConstants.JDK1_5) {
		    sourceType.typeVariables = NoTypeVariables;
		    return;
		}
		sourceType.typeVariables = NoTypeVariables; // safety

		if (sourceType.id == T_Object) { // handle the case of redefining java.lang.Object up front
			problemReporter().objectCannotBeGeneric(referenceContext);
			return; 
		}
		sourceType.typeVariables = createTypeVariables(typeParameters, sourceType);
		sourceType.modifiers |= AccGenericSignature;
	}
	
	private void checkAndSetModifiers() {
		SourceTypeBinding sourceType = referenceContext.binding;
		int modifiers = sourceType.modifiers;
		if ((modifiers & AccAlternateModifierProblem) != 0)
			problemReporter().duplicateModifierForType(sourceType);

		ReferenceBinding enclosingType = sourceType.enclosingType();
		boolean isMemberType = sourceType.isMemberType();
		
		if (isMemberType) {
			// checks for member types before local types to catch local members
			if (enclosingType.isStrictfp())
				modifiers |= AccStrictfp;
			if (enclosingType.isViewedAsDeprecated() && !sourceType.isDeprecated())
				modifiers |= AccDeprecatedImplicitly;
			if (enclosingType.isInterface())
				modifiers |= AccPublic;
		} else if (sourceType.isLocalType()) {
			if (sourceType.isAnonymousType()) 
			    modifiers |= AccFinal;
			Scope scope = this;
			do {
				switch (scope.kind) {
					case METHOD_SCOPE :
						MethodScope methodScope = (MethodScope) scope;
						if (methodScope.isInsideInitializer()) {
							SourceTypeBinding type = ((TypeDeclaration) methodScope.referenceContext).binding;
			
							// inside field declaration ? check field modifier to see if deprecated
							if (methodScope.initializedField != null) {
									// currently inside this field initialization
								if (methodScope.initializedField.isViewedAsDeprecated() && !sourceType.isDeprecated()){
									modifiers |= AccDeprecatedImplicitly;
								}
							} else {
								if (type.isStrictfp())
									modifiers |= AccStrictfp;
								if (type.isViewedAsDeprecated() && !sourceType.isDeprecated()) 
									modifiers |= AccDeprecatedImplicitly;
							}					
						} else {
							MethodBinding method = ((AbstractMethodDeclaration) methodScope.referenceContext).binding;
							if (method != null){
								if (method.isStrictfp())
									modifiers |= AccStrictfp;
								if (method.isViewedAsDeprecated() && !sourceType.isDeprecated())
									modifiers |= AccDeprecatedImplicitly;
							}
						}
						break;
					case CLASS_SCOPE :
						// local member
						if (enclosingType.isStrictfp())
							modifiers |= AccStrictfp;
						if (enclosingType.isViewedAsDeprecated() && !sourceType.isDeprecated())
							modifiers |= AccDeprecatedImplicitly;
						break;
				}
				scope = scope.parent;
			} while (scope != null);
		}
		// after this point, tests on the 16 bits reserved.
		int realModifiers = modifiers & AccJustFlag;

		if ((realModifiers & AccInterface) != 0) {
			// detect abnormal cases for interfaces
			if (isMemberType) {
				int unexpectedModifiers =
					~(AccPublic | AccPrivate | AccProtected | AccStatic | AccAbstract | AccInterface | AccStrictfp);
				if ((realModifiers & unexpectedModifiers) != 0)
					problemReporter().illegalModifierForMemberInterface(sourceType);
				/*
				} else if (sourceType.isLocalType()) { //interfaces cannot be defined inside a method
					int unexpectedModifiers = ~(AccAbstract | AccInterface | AccStrictfp);
					if ((realModifiers & unexpectedModifiers) != 0)
						problemReporter().illegalModifierForLocalInterface(sourceType);
				*/
			} else {
				int unexpectedModifiers = ~(AccPublic | AccAbstract | AccInterface | AccStrictfp);
				if ((realModifiers & unexpectedModifiers) != 0)
					problemReporter().illegalModifierForInterface(sourceType);
			}
			modifiers |= AccAbstract;
		} else {
			// detect abnormal cases for types
			if (isMemberType) { // includes member types defined inside local types
				int unexpectedModifiers =
					~(AccPublic | AccPrivate | AccProtected | AccStatic | AccAbstract | AccFinal | AccStrictfp);
				if ((realModifiers & unexpectedModifiers) != 0)
					problemReporter().illegalModifierForMemberClass(sourceType);
			} else if (sourceType.isLocalType()) {
				int unexpectedModifiers = ~(AccAbstract | AccFinal | AccStrictfp);
				if ((realModifiers & unexpectedModifiers) != 0)
					problemReporter().illegalModifierForLocalClass(sourceType);
			} else {
				int unexpectedModifiers = ~(AccPublic | AccAbstract | AccFinal | AccStrictfp);
				if ((realModifiers & unexpectedModifiers) != 0)
					problemReporter().illegalModifierForClass(sourceType);
			}

			// check that Final and Abstract are not set together
			if ((realModifiers & (AccFinal | AccAbstract)) == (AccFinal | AccAbstract))
				problemReporter().illegalModifierCombinationFinalAbstractForClass(sourceType);
		}

		if (isMemberType) {
			// test visibility modifiers inconsistency, isolate the accessors bits
			if (enclosingType.isInterface()) {
				if ((realModifiers & (AccProtected | AccPrivate)) != 0) {
					problemReporter().illegalVisibilityModifierForInterfaceMemberType(sourceType);

					// need to keep the less restrictive
					if ((realModifiers & AccProtected) != 0)
						modifiers ^= AccProtected;
					if ((realModifiers & AccPrivate) != 0)
						modifiers ^= AccPrivate;
				}
			} else {
				int accessorBits = realModifiers & (AccPublic | AccProtected | AccPrivate);
				if ((accessorBits & (accessorBits - 1)) > 1) {
					problemReporter().illegalVisibilityModifierCombinationForMemberType(sourceType);

					// need to keep the less restrictive
					if ((accessorBits & AccPublic) != 0) {
						if ((accessorBits & AccProtected) != 0)
							modifiers ^= AccProtected;
						if ((accessorBits & AccPrivate) != 0)
							modifiers ^= AccPrivate;
					}
					if ((accessorBits & AccProtected) != 0)
						if ((accessorBits & AccPrivate) != 0)
							modifiers ^= AccPrivate;
				}
			}

			// static modifier test
			if ((realModifiers & AccStatic) == 0) {
				if (enclosingType.isInterface())
					modifiers |= AccStatic;
			} else {
				if (!enclosingType.isStatic())
					// error the enclosing type of a static field must be static or a top-level type
					problemReporter().illegalStaticModifierForMemberType(sourceType);
			}
		}

		sourceType.modifiers = modifiers;
	}
	
	/* This method checks the modifiers of a field.
	*
	* 9.3 & 8.3
	* Need to integrate the check for the final modifiers for nested types
	*
	* Note : A scope is accessible by : fieldBinding.declaringClass.scope
	*/
	private void checkAndSetModifiersForField(FieldBinding fieldBinding, FieldDeclaration fieldDecl) {
		int modifiers = fieldBinding.modifiers;
		if ((modifiers & AccAlternateModifierProblem) != 0)
			problemReporter().duplicateModifierForField(fieldBinding.declaringClass, fieldDecl);

		if (fieldBinding.declaringClass.isInterface()) {
			int expectedValue = AccPublic | AccStatic | AccFinal;
			// set the modifiers
			modifiers |= expectedValue;

			// and then check that they are the only ones
			if ((modifiers & AccJustFlag) != expectedValue)
				problemReporter().illegalModifierForInterfaceField(fieldBinding.declaringClass, fieldDecl);
			fieldBinding.modifiers = modifiers;
			return;
		}

		// after this point, tests on the 16 bits reserved.
		int realModifiers = modifiers & AccJustFlag;
		int unexpectedModifiers =
			~(AccPublic | AccPrivate | AccProtected | AccFinal | AccStatic | AccTransient | AccVolatile);
		if ((realModifiers & unexpectedModifiers) != 0)
			problemReporter().illegalModifierForField(fieldBinding.declaringClass, fieldDecl);

		int accessorBits = realModifiers & (AccPublic | AccProtected | AccPrivate);
		if ((accessorBits & (accessorBits - 1)) > 1) {
			problemReporter().illegalVisibilityModifierCombinationForField(
				fieldBinding.declaringClass,
				fieldDecl);

			// need to keep the less restrictive
			if ((accessorBits & AccPublic) != 0) {
				if ((accessorBits & AccProtected) != 0)
					modifiers ^= AccProtected;
				if ((accessorBits & AccPrivate) != 0)
					modifiers ^= AccPrivate;
			}
			if ((accessorBits & AccProtected) != 0)
				if ((accessorBits & AccPrivate) != 0)
					modifiers ^= AccPrivate;
		}

		if ((realModifiers & (AccFinal | AccVolatile)) == (AccFinal | AccVolatile))
			problemReporter().illegalModifierCombinationFinalVolatileForField(
				fieldBinding.declaringClass,
				fieldDecl);

		if (fieldDecl.initialization == null && (modifiers & AccFinal) != 0) {
			modifiers |= AccBlankFinal;
		}
		fieldBinding.modifiers = modifiers;
	}

	private void checkForInheritedMemberTypes(SourceTypeBinding sourceType) {
		// search up the hierarchy of the sourceType to see if any superType defines a member type
		// when no member types are defined, tag the sourceType & each superType with the HasNoMemberTypes bit
		// assumes super types have already been checked & tagged
		ReferenceBinding currentType = sourceType;
		ReferenceBinding[][] interfacesToVisit = null;
		int lastPosition = -1;
		do {
			if (currentType.hasMemberTypes()) // avoid resolving member types eagerly
				return;

			ReferenceBinding[] itsInterfaces = currentType.superInterfaces();
			if (itsInterfaces != NoSuperInterfaces) {
				if (interfacesToVisit == null)
					interfacesToVisit = new ReferenceBinding[5][];
				if (++lastPosition == interfacesToVisit.length)
					System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition);
				interfacesToVisit[lastPosition] = itsInterfaces;
			}
		} while ((currentType = currentType.superclass()) != null && (currentType.tagBits & HasNoMemberTypes) == 0);

		if (interfacesToVisit != null) {
			// contains the interfaces between the sourceType and any superclass, which was tagged as having no member types
			boolean needToTag = false;
			for (int i = 0; i <= lastPosition; i++) {
				ReferenceBinding[] interfaces = interfacesToVisit[i];
				for (int j = 0, length = interfaces.length; j < length; j++) {
					ReferenceBinding anInterface = interfaces[j];
					if ((anInterface.tagBits & HasNoMemberTypes) == 0) { // skip interface if it already knows it has no member types
						if (anInterface.hasMemberTypes()) // avoid resolving member types eagerly
							return;

						needToTag = true;
						ReferenceBinding[] itsInterfaces = anInterface.superInterfaces();
						if (itsInterfaces != NoSuperInterfaces) {
							if (++lastPosition == interfacesToVisit.length)
								System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition);
							interfacesToVisit[lastPosition] = itsInterfaces;
						}
					}
				}
			}

			if (needToTag) {
				for (int i = 0; i <= lastPosition; i++) {
					ReferenceBinding[] interfaces = interfacesToVisit[i];
					for (int j = 0, length = interfaces.length; j < length; j++)
						interfaces[j].tagBits |= HasNoMemberTypes;
				}
			}
		}

		// tag the sourceType and all of its superclasses, unless they have already been tagged
		currentType = sourceType;
		do {
			currentType.tagBits |= HasNoMemberTypes;
		} while ((currentType = currentType.superclass()) != null && (currentType.tagBits & HasNoMemberTypes) == 0);
	}
	// Perform deferred bound checks for parameterized type references (only done after hierarchy is connected)
	private void  checkParameterizedTypeBounds() {
		TypeReference superclass = referenceContext.superclass;
		if (superclass != null) {
			superclass.checkBounds(this);
		}
		TypeReference[] superinterfaces = referenceContext.superInterfaces;
		if (superinterfaces != null) {
			for (int i = 0, length = superinterfaces.length; i < length; i++) {
				superinterfaces[i].checkBounds(this);
			}
		}
		TypeParameter[] typeParameters = referenceContext.typeParameters;
		if (typeParameters != null) {
			for (int i = 0, paramLength = typeParameters.length; i < paramLength; i++) {
				TypeParameter typeParameter = typeParameters[i];
				TypeReference typeRef = typeParameter.type;
				if (typeRef != null) {
					typeRef.checkBounds(this);

					TypeReference[] boundRefs = typeParameter.bounds;
					if (boundRefs != null)
						for (int j = 0, k = boundRefs.length; j < k; j++)
							boundRefs[j].checkBounds(this);
				}
			}
		}
	}

	private void connectMemberTypes() {
		SourceTypeBinding sourceType = referenceContext.binding;
		if (sourceType.memberTypes != NoMemberTypes)
			for (int i = 0, size = sourceType.memberTypes.length; i < size; i++)
				 ((SourceTypeBinding) sourceType.memberTypes[i]).scope.connectTypeHierarchy();
	}
	/*
		Our current belief based on available JCK tests is:
			inherited member types are visible as a potential superclass.
			inherited interfaces are not visible when defining a superinterface.
	
		Error recovery story:
			ensure the superclass is set to java.lang.Object if a problem is detected
			resolving the superclass.
	
		Answer false if an error was reported against the sourceType.
	*/
	private boolean connectSuperclass() {
		SourceTypeBinding sourceType = referenceContext.binding;
		if (sourceType.id == T_Object) { // handle the case of redefining java.lang.Object up front
			sourceType.superclass = null;
			sourceType.superInterfaces = NoSuperInterfaces;
			if (referenceContext.superclass != null || referenceContext.superInterfaces != null)
				problemReporter().objectCannotHaveSuperTypes(sourceType);
			return true; // do not propagate Object's hierarchy problems down to every subtype
		}
		if (referenceContext.superclass == null) {
			sourceType.superclass = getJavaLangObject();
			return !detectCycle(sourceType, sourceType.superclass, null);
		}
		TypeReference superclassRef = referenceContext.superclass;
		ReferenceBinding superclass = findSupertype(superclassRef);
		if (superclass != null) { // is null if a cycle was detected cycle or a problem
			if (superclass.isInterface()) {
				problemReporter().superclassMustBeAClass(sourceType, superclassRef, superclass);
			} else if (superclass.isFinal()) {
				problemReporter().classExtendFinalClass(sourceType, superclassRef, superclass);
			} else if ((superclass.tagBits & TagBits.HasWildcard) != 0) {
				problemReporter().superTypeCannotUseWildcard(sourceType, superclassRef, superclass);
			} else {
				// only want to reach here when no errors are reported
				sourceType.superclass = superclass;
				return true;
			}
		}
		sourceType.tagBits |= HierarchyHasProblems;
		sourceType.superclass = getJavaLangObject();
		if ((sourceType.superclass.tagBits & BeginHierarchyCheck) == 0)
			detectCycle(sourceType, sourceType.superclass, null);
		return false; // reported some error against the source type
	}

	/*
		Our current belief based on available JCK 1.3 tests is:
			inherited member types are visible as a potential superclass.
			inherited interfaces are visible when defining a superinterface.
	
		Error recovery story:
			ensure the superinterfaces contain only valid visible interfaces.
	
		Answer false if an error was reported against the sourceType.
	*/
	private boolean connectSuperInterfaces() {
		SourceTypeBinding sourceType = referenceContext.binding;
		sourceType.superInterfaces = NoSuperInterfaces;
		if (referenceContext.superInterfaces == null)
			return true;
		if (sourceType.id == T_Object) // already handled the case of redefining java.lang.Object
			return true;

		boolean noProblems = true;
		int length = referenceContext.superInterfaces.length;
		ReferenceBinding[] interfaceBindings = new ReferenceBinding[length];
		int count = 0;
		nextInterface : for (int i = 0; i < length; i++) {
		    TypeReference superInterfaceRef = referenceContext.superInterfaces[i];
			ReferenceBinding superInterface = findSupertype(superInterfaceRef);
			if (superInterface == null) { // detected cycle
				sourceType.tagBits |= HierarchyHasProblems;
				noProblems = false;
				continue nextInterface;
			}
			superInterfaceRef.resolvedType = superInterface; // hold onto the problem type
			// Check for a duplicate interface once the name is resolved, otherwise we may be confused (ie : a.b.I and c.d.I)
			for (int k = 0; k < count; k++) {
				if (interfaceBindings[k] == superInterface) {
					// should this be treated as a warning?
					problemReporter().duplicateSuperinterface(sourceType, referenceContext, superInterface);
					continue nextInterface;
				}
			}
			if (superInterface.isClass()) {
				problemReporter().superinterfaceMustBeAnInterface(sourceType, superInterfaceRef, superInterface);
				sourceType.tagBits |= HierarchyHasProblems;
				noProblems = false;
				continue nextInterface;
			}
			if ((superInterface.tagBits & TagBits.HasWildcard) != 0) {
				problemReporter().superTypeCannotUseWildcard(sourceType, superInterfaceRef, superInterface);
				sourceType.tagBits |= HierarchyHasProblems;
				noProblems = false;
				continue nextInterface;
			}
			ReferenceBinding invalid = findAmbiguousInterface(superInterface, sourceType);
			if (invalid != null) {
				ReferenceBinding generic = null;
				if (superInterface.isParameterizedType())
					generic = ((ParameterizedTypeBinding) superInterface).type;
				else if (invalid.isParameterizedType())
					generic = ((ParameterizedTypeBinding) invalid).type;
				problemReporter().superinterfacesCollide(generic, referenceContext, superInterface, invalid);
				sourceType.tagBits |= HierarchyHasProblems;
				noProblems = false;
				continue nextInterface;
			}

			// only want to reach here when no errors are reported
			interfaceBindings[count++] = superInterface;
		}
		// hold onto all correctly resolved superinterfaces
		if (count > 0) {
			if (count != length)
				System.arraycopy(interfaceBindings, 0, interfaceBindings = new ReferenceBinding[count], 0, count);
			sourceType.superInterfaces = interfaceBindings;
		}
		return noProblems;
	}
	
	void connectTypeHierarchy() {
		SourceTypeBinding sourceType = referenceContext.binding;
		if ((sourceType.tagBits & BeginHierarchyCheck) == 0) {
			sourceType.tagBits |= BeginHierarchyCheck;
			boolean noProblems = connectTypeVariables(referenceContext.typeParameters);
			noProblems &= connectSuperclass();
			noProblems &= connectSuperInterfaces();
			sourceType.tagBits |= EndHierarchyCheck;
			if (noProblems && sourceType.isHierarchyInconsistent())
				problemReporter().hierarchyHasProblems(sourceType);
		}
		// Perform deferred bound checks for parameterized type references (only done after hierarchy is connected)
		checkParameterizedTypeBounds();
		connectMemberTypes();
		try {
			checkForInheritedMemberTypes(sourceType);
		} catch (AbortCompilation e) {
			e.updateContext(referenceContext, referenceCompilationUnit().compilationResult);
			throw e;
		}
	}
	
	private void connectTypeHierarchyWithoutMembers() {
		// must ensure the imports are resolved
		if (parent instanceof CompilationUnitScope) {
			if (((CompilationUnitScope) parent).imports == null)
				 ((CompilationUnitScope) parent).checkAndSetImports();
		} else if (parent instanceof ClassScope) {
			// ensure that the enclosing type has already been checked
			 ((ClassScope) parent).connectTypeHierarchyWithoutMembers();
		}

		// double check that the hierarchy search has not already begun...
		SourceTypeBinding sourceType = referenceContext.binding;
		if ((sourceType.tagBits & BeginHierarchyCheck) != 0)
			return;

		sourceType.tagBits |= BeginHierarchyCheck;
		boolean noProblems = connectTypeVariables(referenceContext.typeParameters);
		noProblems &= connectSuperclass();
		noProblems &= connectSuperInterfaces();
		sourceType.tagBits |= EndHierarchyCheck;
		if (noProblems && sourceType.isHierarchyInconsistent())
			problemReporter().hierarchyHasProblems(sourceType);
	}

	public boolean detectCycle(TypeBinding superType, TypeReference reference, TypeBinding[] argTypes) {
		if (!(superType instanceof ReferenceBinding)) return false;

		if (argTypes != null) {
			for (int i = 0, l = argTypes.length; i < l; i++) {
				TypeBinding argType = argTypes[i].leafComponentType();
				if ((argType.tagBits & BeginHierarchyCheck) == 0 && argType instanceof SourceTypeBinding)
			    	// ensure if this is a source argument type that it has already been checked
			    	((SourceTypeBinding) argType).scope.connectTypeHierarchyWithoutMembers();
			}
		}

		if (reference == this.superTypeReference) { // see findSuperType()
			if (superType.isTypeVariable())
				return false; // error case caught in resolveSuperType()
			// abstract class X<K,V> implements java.util.Map<K,V>
			//    static abstract class M<K,V> implements Entry<K,V>
			if (superType.isParameterizedType())
				superType = ((ParameterizedTypeBinding) superType).type;
			compilationUnitScope().recordSuperTypeReference(superType); // to record supertypes
			return detectCycle(referenceContext.binding, (ReferenceBinding) superType, reference);
		}

		if ((superType.tagBits & BeginHierarchyCheck) == 0 && superType instanceof SourceTypeBinding)
			// ensure if this is a source superclass that it has already been checked
			((SourceTypeBinding) superType).scope.connectTypeHierarchyWithoutMembers();
		return false;
	}

	// Answer whether a cycle was found between the sourceType & the superType
	private boolean detectCycle(SourceTypeBinding sourceType, ReferenceBinding superType, TypeReference reference) {
		if (superType.isRawType())
			superType = ((RawTypeBinding) superType).type;
		// by this point the superType must be a binary or source type

		if (sourceType == superType) {
			problemReporter().hierarchyCircularity(sourceType, superType, reference);
			sourceType.tagBits |= HierarchyHasProblems;
			return true;
		}

		if (superType.isMemberType()) {
			ReferenceBinding current = superType.enclosingType();
			do {
				if (current.isHierarchyBeingConnected()) {
					problemReporter().hierarchyCircularity(sourceType, current, reference);
					sourceType.tagBits |= HierarchyHasProblems;
					current.tagBits |= HierarchyHasProblems;
					return true;
				}
			} while ((current = current.enclosingType()) != null);
		}

		if (superType.isBinaryBinding()) {
			// force its superclass & superinterfaces to be found... 2 possibilities exist - the source type is included in the hierarchy of:
			//		- a binary type... this case MUST be caught & reported here
			//		- another source type... this case is reported against the other source type
			boolean hasCycle = false;
			if (superType.superclass() != null) {
				if (sourceType == superType.superclass()) {
					problemReporter().hierarchyCircularity(sourceType, superType, reference);
					sourceType.tagBits |= HierarchyHasProblems;
					superType.tagBits |= HierarchyHasProblems;
					return true;
				}
				ReferenceBinding parentType = superType.superclass();
				if (parentType.isParameterizedType())
					parentType = ((ParameterizedTypeBinding) parentType).type;
				hasCycle |= detectCycle(sourceType, parentType, reference);
				if ((parentType.tagBits & HierarchyHasProblems) != 0) {
					sourceType.tagBits |= HierarchyHasProblems;
					parentType.tagBits |= HierarchyHasProblems; // propagate down the hierarchy
				}
			}

			ReferenceBinding[] itsInterfaces = superType.superInterfaces();
			if (itsInterfaces != NoSuperInterfaces) {
				for (int i = 0, length = itsInterfaces.length; i < length; i++) {
					ReferenceBinding anInterface = itsInterfaces[i];
					if (sourceType == anInterface) {
						problemReporter().hierarchyCircularity(sourceType, superType, reference);
						sourceType.tagBits |= HierarchyHasProblems;
						superType.tagBits |= HierarchyHasProblems;
						return true;
					}
					if (anInterface.isParameterizedType())
						anInterface = ((ParameterizedTypeBinding) anInterface).type;
					hasCycle |= detectCycle(sourceType, anInterface, reference);
					if ((anInterface.tagBits & HierarchyHasProblems) != 0) {
						sourceType.tagBits |= HierarchyHasProblems;
						superType.tagBits |= HierarchyHasProblems;
					}
				}
			}
			return hasCycle;
		}

		if (superType.isHierarchyBeingConnected()) {
			if (((SourceTypeBinding) superType).scope.superTypeReference != null) { // if null then its connecting its type variables
				problemReporter().hierarchyCircularity(sourceType, superType, reference);
				sourceType.tagBits |= HierarchyHasProblems;
				superType.tagBits |= HierarchyHasProblems;
				return true;
			}
		}
		if ((superType.tagBits & BeginHierarchyCheck) == 0)
			// ensure if this is a source superclass that it has already been checked
			((SourceTypeBinding) superType).scope.connectTypeHierarchyWithoutMembers();
		if ((superType.tagBits & HierarchyHasProblems) != 0)
			sourceType.tagBits |= HierarchyHasProblems;
		return false;
	}

	private ReferenceBinding findAmbiguousInterface(ReferenceBinding newInterface, ReferenceBinding currentType) {
		TypeBinding newErasure = newInterface.erasure();
		if (newInterface == newErasure) return null;

		ReferenceBinding[][] interfacesToVisit = new ReferenceBinding[5][];
		int lastPosition = -1;
		do {
			ReferenceBinding[] itsInterfaces = currentType.superInterfaces();
			if (itsInterfaces != NoSuperInterfaces) {
				if (++lastPosition == interfacesToVisit.length)
					System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition);
				interfacesToVisit[lastPosition] = itsInterfaces;
			}
		} while ((currentType = currentType.superclass()) != null);

		for (int i = 0; i <= lastPosition; i++) {
			ReferenceBinding[] interfaces = interfacesToVisit[i];
			for (int j = 0, length = interfaces.length; j < length; j++) {
				currentType = interfaces[j];
				if (currentType.erasure() == newErasure)
					if (currentType != newInterface)
						return currentType;

				ReferenceBinding[] itsInterfaces = currentType.superInterfaces();
				if (itsInterfaces != NoSuperInterfaces) {
					if (++lastPosition == interfacesToVisit.length)
						System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition);
					interfacesToVisit[lastPosition] = itsInterfaces;
				}
			}
		}
		return null;
	}

	private ReferenceBinding findSupertype(TypeReference typeReference) {
		try {
			typeReference.aboutToResolve(this); // allows us to trap completion & selection nodes
			compilationUnitScope().recordQualifiedReference(typeReference.getTypeName());
			this.superTypeReference = typeReference;
			ReferenceBinding superType = (ReferenceBinding) typeReference.resolveSuperType(this);
			this.superTypeReference = null;
			return superType;
		} catch (AbortCompilation e) {
			e.updateContext(typeReference, referenceCompilationUnit().compilationResult);
			throw e;
		}			
	}

	/* Answer the problem reporter to use for raising new problems.
	*
	* Note that as a side-effect, this updates the current reference context
	* (unit, type or method) in case the problem handler decides it is necessary
	* to abort.
	*/
	public ProblemReporter problemReporter() {
		MethodScope outerMethodScope;
		if ((outerMethodScope = outerMostMethodScope()) == null) {
			ProblemReporter problemReporter = referenceCompilationUnit().problemReporter;
			problemReporter.referenceContext = referenceContext;
			return problemReporter;
		}
		return outerMethodScope.problemReporter();
	}

	/* Answer the reference type of this scope.
	* It is the nearest enclosing type of this scope.
	*/
	public TypeDeclaration referenceType() {
		return referenceContext;
	}
	
	public String toString() {
		if (referenceContext != null)
			return "--- Class Scope ---\n\n"  //$NON-NLS-1$
							+ referenceContext.binding.toString();
		return "--- Class Scope ---\n\n Binding not initialized" ; //$NON-NLS-1$
	}
}
