/*******************************************************************************
 * Copyright (c) 2000, 2003 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 net.sourceforge.phpdt.internal.compiler.lookup;

import net.sourceforge.phpdt.core.compiler.CharOperation;
import net.sourceforge.phpdt.internal.compiler.env.IDependent;

/*
 * Not all fields defined by this type (& its subclasses) are initialized when
 * it is created. Some are initialized only when needed.
 * 
 * Accessors have been provided for some public fields so all TypeBindings have
 * the same API... but access public fields directly whenever possible.
 * Non-public fields have accessors which should be used everywhere you expect
 * the field to be initialized.
 * 
 * null is NOT a valid value for a non-public field... it just means the field
 * is not initialized.
 */

abstract public class ReferenceBinding extends TypeBinding implements
		IDependent {
	public char[][] compoundName;

	public char[] sourceName;

	public int modifiers;

	public PackageBinding fPackage;

	char[] fileName;

	char[] constantPoolName;

	char[] signature;

	public FieldBinding[] availableFields() {
		return fields();
	}

	public MethodBinding[] availableMethods() {
		return methods();
	}

	/*
	 * Answer true if the receiver can be instantiated
	 */

	public boolean canBeInstantiated() {
		return !(isAbstract() || isInterface());
	}

	/*
	 * Answer true if the receiver is visible to the invocationPackage.
	 */

	public final boolean canBeSeenBy(PackageBinding invocationPackage) {
		if (isPublic())
			return true;
		if (isPrivate())
			return false;

		// isProtected() or isDefault()
		return invocationPackage == fPackage;
	}

	/*
	 * Answer true if the receiver is visible to the receiverType and the
	 * invocationType.
	 */

	public final boolean canBeSeenBy(ReferenceBinding receiverType,
			SourceTypeBinding invocationType) {
		if (isPublic())
			return true;

		if (invocationType == this && invocationType == receiverType)
			return true;

		if (isProtected()) {

			// answer true if the invocationType is the declaringClass or they
			// are in the same package
			// OR the invocationType is a subclass of the declaringClass
			// AND the invocationType is the invocationType or its subclass
			// OR the type is a static method accessed directly through a type
			// OR previous assertions are true for one of the enclosing type
			if (invocationType == this)
				return true;
			if (invocationType.fPackage == fPackage)
				return true;

			ReferenceBinding currentType = invocationType;
			ReferenceBinding declaringClass = enclosingType(); // protected
																// types always
																// have an
																// enclosing one
			if (declaringClass == null)
				return false; // could be null if incorrect top-level
								// protected type
			// int depth = 0;
			do {
				if (declaringClass == invocationType)
					return true;
				if (declaringClass.isSuperclassOf(currentType))
					return true;
				// depth++;
				currentType = currentType.enclosingType();
			} while (currentType != null);
			return false;
		}

		if (isPrivate()) {
			// answer true if the receiverType is the receiver or its
			// enclosingType
			// AND the invocationType and the receiver have a common
			// enclosingType
			if (!(receiverType == this || receiverType == enclosingType()))
				return false;

			if (invocationType != this) {
				ReferenceBinding outerInvocationType = invocationType;
				ReferenceBinding temp = outerInvocationType.enclosingType();
				while (temp != null) {
					outerInvocationType = temp;
					temp = temp.enclosingType();
				}

				ReferenceBinding outerDeclaringClass = this;
				temp = outerDeclaringClass.enclosingType();
				while (temp != null) {
					outerDeclaringClass = temp;
					temp = temp.enclosingType();
				}
				if (outerInvocationType != outerDeclaringClass)
					return false;
			}
			return true;
		}

		// isDefault()
		if (invocationType.fPackage != fPackage)
			return false;

		ReferenceBinding type = receiverType;
		ReferenceBinding declaringClass = enclosingType() == null ? this
				: enclosingType();
		do {
			if (declaringClass == type)
				return true;
			if (fPackage != type.fPackage)
				return false;
		} while ((type = type.superclass()) != null);
		return false;
	}

	/*
	 * Answer true if the receiver is visible to the type provided by the scope.
	 */

	public final boolean canBeSeenBy(Scope scope) {

		if (isPublic())
			return true;

		if (scope.kind == Scope.COMPILATION_UNIT_SCOPE) {
			return this.canBeSeenBy(((CompilationUnitScope) scope).fPackage);
		}

		SourceTypeBinding invocationType = scope.enclosingSourceType();
		if (invocationType == this)
			return true;

		if (isProtected()) {
			// answer true if the invocationType is the declaringClass or they
			// are in the same package
			// OR the invocationType is a subclass of the declaringClass
			// AND the invocationType is the invocationType or its subclass
			// OR the type is a static method accessed directly through a type
			// OR previous assertions are true for one of the enclosing type
			if (invocationType.fPackage == fPackage)
				return true;

			ReferenceBinding currentType = invocationType;
			ReferenceBinding declaringClass = enclosingType(); // protected
																// types always
																// have an
																// enclosing one
			if (declaringClass == null)
				return false; // could be null if incorrect top-level
								// protected type
			// int depth = 0;
			do {
				if (declaringClass == invocationType)
					return true;
				if (declaringClass.isSuperclassOf(currentType))
					return true;
				// depth++;
				currentType = currentType.enclosingType();
			} while (currentType != null);
			return false;
		}
		if (isPrivate()) {
			// answer true if the receiver and the invocationType have a common
			// enclosingType
			// already know they are not the identical type
			ReferenceBinding outerInvocationType = invocationType;
			ReferenceBinding temp = outerInvocationType.enclosingType();
			while (temp != null) {
				outerInvocationType = temp;
				temp = temp.enclosingType();
			}

			ReferenceBinding outerDeclaringClass = this;
			temp = outerDeclaringClass.enclosingType();
			while (temp != null) {
				outerDeclaringClass = temp;
				temp = temp.enclosingType();
			}
			return outerInvocationType == outerDeclaringClass;
		}

		// isDefault()
		return invocationType.fPackage == fPackage;
	}

	// public void computeId() {
	// if (compoundName.length != 3) {
	// if (compoundName.length == 4 &&
	// CharOperation.equals(JAVA_LANG_REFLECT_CONSTRUCTOR, compoundName)) {
	// id = T_JavaLangReflectConstructor;
	// return;
	// }
	// return; // all other types are in java.*.*
	// }
	//
	// if (!CharOperation.equals(JAVA, compoundName[0]))
	// return; // assumes we only look up types in java
	//
	// if (!CharOperation.equals(LANG, compoundName[1])) {
	// if (CharOperation.equals(JAVA_IO_PRINTSTREAM, compoundName)) {
	// id = T_JavaIoPrintStream;
	// return;
	// }
	// return; // all other types are in java.lang
	// }
	//
	// if (CharOperation.equals(JAVA_LANG_OBJECT, compoundName)) {
	// id = T_JavaLangObject;
	// return;
	// }
	// if (CharOperation.equals(JAVA_LANG_STRING, compoundName)) {
	// id = T_JavaLangString;
	// return;
	// }
	//
	// // well-known exception types
	// if (CharOperation.equals(JAVA_LANG_THROWABLE, compoundName)) {
	// id = T_JavaLangThrowable;
	// return;
	// }
	// if (CharOperation.equals(JAVA_LANG_ERROR, compoundName)) {
	// id = T_JavaLangError;
	// return;
	// }
	// if (CharOperation.equals(JAVA_LANG_EXCEPTION, compoundName)) {
	// id = T_JavaLangException;
	// return;
	// }
	// if (CharOperation.equals(JAVA_LANG_CLASSNOTFOUNDEXCEPTION, compoundName))
	// {
	// id = T_JavaLangClassNotFoundException;
	// return;
	// }
	// if (CharOperation.equals(JAVA_LANG_NOCLASSDEFERROR, compoundName)) {
	// id = T_JavaLangNoClassDefError;
	// return;
	// }
	//
	// // other well-known types
	// if (CharOperation.equals(JAVA_LANG_CLASS, compoundName)) {
	// id = T_JavaLangClass;
	// return;
	// }
	// if (CharOperation.equals(JAVA_LANG_STRINGBUFFER, compoundName)) {
	// id = T_JavaLangStringBuffer;
	// return;
	// }
	// if (CharOperation.equals(JAVA_LANG_SYSTEM, compoundName)) {
	// id = T_JavaLangSystem;
	// return;
	// }
	//
	// if (CharOperation.equals(JAVA_LANG_INTEGER, compoundName)) {
	// id = T_JavaLangInteger;
	// return;
	// }
	//
	// if (CharOperation.equals(JAVA_LANG_BYTE, compoundName)) {
	// id = T_JavaLangByte;
	// return;
	// }
	//
	// if (CharOperation.equals(JAVA_LANG_CHARACTER, compoundName)) {
	// id = T_JavaLangCharacter;
	// return;
	// }
	//
	// if (CharOperation.equals(JAVA_LANG_FLOAT, compoundName)) {
	// id = T_JavaLangFloat;
	// return;
	// }
	//
	// if (CharOperation.equals(JAVA_LANG_DOUBLE, compoundName)) {
	// id = T_JavaLangDouble;
	// return;
	// }
	//
	// if (CharOperation.equals(JAVA_LANG_BOOLEAN, compoundName)) {
	// id = T_JavaLangBoolean;
	// return;
	// }
	//
	// if (CharOperation.equals(JAVA_LANG_SHORT, compoundName)) {
	// id = T_JavaLangShort;
	// return;
	// }
	//
	// if (CharOperation.equals(JAVA_LANG_LONG, compoundName)) {
	// id = T_JavaLangLong;
	// return;
	// }
	//
	// if (CharOperation.equals(JAVA_LANG_VOID, compoundName)) {
	// id = T_JavaLangVoid;
	// return;
	// }
	//	
	// if (CharOperation.equals(JAVA_LANG_ASSERTIONERROR, compoundName)) {
	// id = T_JavaLangAssertionError;
	// return;
	// }
	// }
	/*
	 * Answer the receiver's constant pool name.
	 * 
	 * NOTE: This method should only be used during/after code gen.
	 */

	public char[] constantPoolName() /* java/lang/Object */{
		if (constantPoolName != null)
			return constantPoolName;
		return constantPoolName = CharOperation.concatWith(compoundName, '/');
	}

	String debugName() {
		return (compoundName != null) ? new String(readableName())
				: "UNNAMED TYPE"; //$NON-NLS-1$
	}

	public final int depth() {
		int depth = 0;
		ReferenceBinding current = this;
		while ((current = current.enclosingType()) != null)
			depth++;
		return depth;
	}

	/*
	 * Answer the receiver's enclosing type... null if the receiver is a top
	 * level type.
	 */

	public ReferenceBinding enclosingType() {
		return null;
	}

	public final ReferenceBinding enclosingTypeAt(int relativeDepth) {
		ReferenceBinding current = this;
		while (relativeDepth-- > 0 && current != null)
			current = current.enclosingType();
		return current;
	}

	public int fieldCount() {
		return fields().length;
	}

	public FieldBinding[] fields() {
		return NoFields;
	}

	public final int getAccessFlags() {
		return modifiers & AccJustFlag;
	}

	public MethodBinding getExactConstructor(TypeBinding[] argumentTypes) {
		return null;
	}

	public MethodBinding getExactMethod(char[] selector,
			TypeBinding[] argumentTypes) {
		return null;
	}

	public FieldBinding getField(char[] fieldName) {
		return null;
	}

	/**
	 * Answer the file name which defines the type.
	 * 
	 * The path part (optional) must be separated from the actual file proper
	 * name by a java.io.File.separator.
	 * 
	 * The proper file name includes the suffix extension (e.g. ".java")
	 * 
	 * e.g. "c:/com/ibm/compiler/java/api/Compiler.java"
	 */

	public char[] getFileName() {
		return fileName;
	}

	public ReferenceBinding getMemberType(char[] typeName) {
		ReferenceBinding[] memberTypes = memberTypes();
		for (int i = memberTypes.length; --i >= 0;)
			if (CharOperation.equals(memberTypes[i].sourceName, typeName))
				return memberTypes[i];
		return null;
	}

	public MethodBinding[] getMethods(char[] selector) {
		return NoMethods;
	}

	public PackageBinding getPackage() {
		return fPackage;
	}

	/*
	 * Answer true if the receiver implements anInterface or is identical to
	 * anInterface. If searchHierarchy is true, then also search the receiver's
	 * superclasses.
	 * 
	 * NOTE: Assume that anInterface is an interface.
	 */

	public boolean implementsInterface(ReferenceBinding anInterface,
			boolean searchHierarchy) {
		if (this == anInterface)
			return true;

		ReferenceBinding[][] interfacesToVisit = new ReferenceBinding[5][];
		int lastPosition = -1;
		ReferenceBinding currentType = this;
		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 (searchHierarchy
				&& (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++) {
				if ((currentType = interfaces[j]) == anInterface)
					return true;

				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 false;
	}

	// Internal method... assume its only sent to classes NOT interfaces

	boolean implementsMethod(MethodBinding method) {
		ReferenceBinding type = this;
		while (type != null) {
			MethodBinding[] methods = type.getMethods(method.selector);
			for (int i = methods.length; --i >= 0;)
				if (methods[i].areParametersEqual(method))
					return true;
			type = type.superclass();
		}
		return false;
	}

	/*
	 * Answer true if the receiver is an abstract type
	 */

	public final boolean isAbstract() {
		return (modifiers & AccAbstract) != 0;
	}

	public final boolean isAnonymousType() {
		return (tagBits & IsAnonymousType) != 0;
	}

	public final boolean isBinaryBinding() {
		return (tagBits & IsBinaryBinding) != 0;
	}

	public final boolean isClass() {
		return (modifiers & AccInterface) == 0;
	}

	/*
	 * Answer true if the receiver type can be assigned to the argument type
	 * (right)
	 */

	public boolean isCompatibleWith(TypeBinding right) {
		if (right == this)
			return true;
		if (right.id == T_Object)
			return true;
		if (!(right instanceof ReferenceBinding))
			return false;

		ReferenceBinding referenceBinding = (ReferenceBinding) right;
		if (referenceBinding.isInterface())
			return implementsInterface(referenceBinding, true);
		if (isInterface()) // Explicit conversion from an interface to a class
							// is not allowed
			return false;
		return referenceBinding.isSuperclassOf(this);
	}

	/*
	 * Answer true if the receiver has default visibility
	 */

	public final boolean isDefault() {
		return (modifiers & (AccPublic | AccProtected | AccPrivate)) == 0;
	}

	/*
	 * Answer true if the receiver is a deprecated type
	 */

	public final boolean isDeprecated() {
		return (modifiers & AccDeprecated) != 0;
	}

	/*
	 * Answer true if the receiver is final and cannot be subclassed
	 */

	public final boolean isFinal() {
		return (modifiers & AccFinal) != 0;
	}

	public final boolean isInterface() {
		return (modifiers & AccInterface) != 0;
	}

	public final boolean isLocalType() {
		return (tagBits & IsLocalType) != 0;
	}

	public final boolean isMemberType() {
		return (tagBits & IsMemberType) != 0;
	}

	public final boolean isNestedType() {
		return (tagBits & IsNestedType) != 0;
	}

	/*
	 * Answer true if the receiver has private visibility
	 */

	public final boolean isPrivate() {
		return (modifiers & AccPrivate) != 0;
	}

	/*
	 * Answer true if the receiver has private visibility and is used locally
	 */

	public final boolean isPrivateUsed() {
		return (modifiers & AccPrivateUsed) != 0;
	}

	/*
	 * Answer true if the receiver has protected visibility
	 */

	public final boolean isProtected() {
		return (modifiers & AccProtected) != 0;
	}

	/*
	 * Answer true if the receiver has public visibility
	 */

	public final boolean isPublic() {
		return (modifiers & AccPublic) != 0;
	}

	/*
	 * Answer true if the receiver is a static member type (or toplevel)
	 */

	public final boolean isStatic() {
		return (modifiers & (AccStatic | AccInterface)) != 0
				|| (tagBits & IsNestedType) == 0;
	}

	/*
	 * Answer true if all float operations must adher to IEEE 754 float/double
	 * rules
	 */

	// public final boolean isStrictfp() {
	// return (modifiers & AccStrictfp) != 0;
	// }
	/*
	 * Answer true if the receiver is in the superclass hierarchy of aType
	 * 
	 * NOTE: Object.isSuperclassOf(Object) -> false
	 */

	public boolean isSuperclassOf(ReferenceBinding type) {
		do {
			if (this == (type = type.superclass()))
				return true;
		} while (type != null);

		return false;
	}

	/*
	 * Answer true if the receiver is deprecated (or any of its enclosing types)
	 */

	public final boolean isViewedAsDeprecated() {
		return (modifiers & AccDeprecated) != 0
				|| (modifiers & AccDeprecatedImplicitly) != 0;
	}

	public ReferenceBinding[] memberTypes() {
		return NoMemberTypes;
	}

	public MethodBinding[] methods() {
		return NoMethods;
	}

	/**
	 * Answer the source name for the type. In the case of member types, as the
	 * qualified name from its top level type. For example, for a member type N
	 * defined inside M & A: "A.M.N".
	 */

	public char[] qualifiedSourceName() {
		if (isMemberType()) {
			return CharOperation.concat(enclosingType().qualifiedSourceName(),
					sourceName(), '.');
		} else {
			return sourceName();
		}
	}

	public char[] readableName() /* java.lang.Object */{
		if (isMemberType())
			return CharOperation.concat(enclosingType().readableName(),
					sourceName, '.');
		else
			return CharOperation.concatWith(compoundName, '.');
	}

	public char[] shortReadableName() /* Object */{
		if (isMemberType())
			return CharOperation.concat(enclosingType().shortReadableName(),
					sourceName, '.');
		else
			return sourceName;
	}

	/*
	 * Answer the receiver's signature.
	 * 
	 * NOTE: This method should only be used during/after code gen.
	 */

	public char[] signature() /* Ljava/lang/Object; */{
		if (signature != null)
			return signature;

		return signature = CharOperation.concat('L', constantPoolName(), ';');
	}

	public char[] sourceName() {
		return sourceName;
	}

	public ReferenceBinding superclass() {
		return null;
	}

	public ReferenceBinding[] superInterfaces() {
		return NoSuperInterfaces;
	}

	public ReferenceBinding[] syntheticEnclosingInstanceTypes() {
		if (isStatic())
			return null;

		ReferenceBinding enclosingType = enclosingType();
		if (enclosingType == null)
			return null;
		else
			return new ReferenceBinding[] { enclosingType };
	}

	public SyntheticArgumentBinding[] syntheticOuterLocalVariables() {
		return null; // is null if no enclosing instances are required
	}
}
