/*******************************************************************************
 * 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.ast;

import org.eclipse.wst.jsdt.internal.compiler.ASTVisitor;
import org.eclipse.wst.jsdt.internal.compiler.impl.*;
import org.eclipse.wst.jsdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.wst.jsdt.internal.compiler.codegen.*;
import org.eclipse.wst.jsdt.internal.compiler.flow.*;
import org.eclipse.wst.jsdt.internal.compiler.lookup.*;

public class FieldReference extends Reference implements InvocationSite {

	public Expression receiver;
	public char[] token;
	public FieldBinding binding;															// exact binding resulting from lookup
	protected FieldBinding codegenBinding;									// actual binding used for code generation (if no synthetic accessor)
	public MethodBinding[] syntheticAccessors; // [0]=read accessor [1]=write accessor
	public static final int READ = 0;
	public static final int WRITE = 1;
	
	public long nameSourcePosition; //(start<<32)+end
	public TypeBinding receiverType;
	public TypeBinding genericCast;
	
	public FieldReference(char[] source, long pos) {

		token = source;
		nameSourcePosition = pos;
		//by default the position are the one of the field (not true for super access)
		sourceStart = (int) (pos >>> 32);
		sourceEnd = (int) (pos & 0x00000000FFFFFFFFL);
		bits |= BindingIds.FIELD;

	}

	public FlowInfo analyseAssignment(
		BlockScope currentScope,
		FlowContext flowContext,
		FlowInfo flowInfo,
		Assignment assignment,
		boolean isCompound) {

		// compound assignment extra work
		if (isCompound) { // check the variable part is initialized if blank final
			if (binding.isBlankFinal()
				&& receiver.isThis()
				&& currentScope.allowBlankFinalFieldAssignment(binding)
				&& (!flowInfo.isDefinitelyAssigned(binding))) {
				currentScope.problemReporter().uninitializedBlankFinalField(binding, this);
				// we could improve error msg here telling "cannot use compound assignment on final blank field"
			}
			manageSyntheticAccessIfNecessary(currentScope, flowInfo, true /*read-access*/);
		}
		flowInfo =
			receiver
				.analyseCode(currentScope, flowContext, flowInfo, !binding.isStatic())
				.unconditionalInits();
		if (assignment.expression != null) {
			flowInfo =
				assignment
					.expression
					.analyseCode(currentScope, flowContext, flowInfo)
					.unconditionalInits();
		}
		manageSyntheticAccessIfNecessary(currentScope, flowInfo, false /*write-access*/);

		// check if assigning a final field 
		if (binding.isFinal()) {
			// in a context where it can be assigned?
			if (binding.isBlankFinal()
				&& !isCompound
				&& receiver.isThis()
				&& !(receiver instanceof QualifiedThisReference)
				&& ((receiver.bits & ParenthesizedMASK) == 0) // (this).x is forbidden
				&& currentScope.allowBlankFinalFieldAssignment(binding)) {
				if (flowInfo.isPotentiallyAssigned(binding)) {
					currentScope.problemReporter().duplicateInitializationOfBlankFinalField(
						binding,
						this);
				} else {
					flowContext.recordSettingFinal(binding, this, flowInfo);
				}
				flowInfo.markAsDefinitelyAssigned(binding);
			} else {
				// assigning a final field outside an initializer or constructor or wrong reference
				currentScope.problemReporter().cannotAssignToFinalField(binding, this);
			}
		}
		return flowInfo;
	}

	public FlowInfo analyseCode(
		BlockScope currentScope,
		FlowContext flowContext,
		FlowInfo flowInfo) {

		return analyseCode(currentScope, flowContext, flowInfo, true);
	}

	public FlowInfo analyseCode(
		BlockScope currentScope,
		FlowContext flowContext,
		FlowInfo flowInfo,
		boolean valueRequired) {

		receiver.analyseCode(currentScope, flowContext, flowInfo, !binding.isStatic());
		if (valueRequired) {
			manageSyntheticAccessIfNecessary(currentScope, flowInfo, true /*read-access*/);
		}
		return flowInfo;
	}

	/**
	 * @see org.eclipse.wst.jsdt.internal.compiler.ast.Expression#computeConversion(org.eclipse.wst.jsdt.internal.compiler.lookup.Scope, org.eclipse.wst.jsdt.internal.compiler.lookup.TypeBinding, org.eclipse.wst.jsdt.internal.compiler.lookup.TypeBinding)
	 */
	public void computeConversion(Scope scope, TypeBinding runtimeTimeType, TypeBinding compileTimeType) {
		if (runtimeTimeType == null || compileTimeType == null)
			return;		
		// set the generic cast after the fact, once the type expectation is fully known (no need for strict cast)
		if (this.binding != null && this.binding.isValidBinding()) {
			FieldBinding originalBinding = this.binding.original();
			if (originalBinding != this.binding) {
			    // extra cast needed if method return type has type variable
			    if ((originalBinding.type.tagBits & TagBits.HasTypeVariable) != 0 && runtimeTimeType.id != T_Object) {
			        this.genericCast = originalBinding.type.genericCast(runtimeTimeType);
			    }
			}
		} 	
		super.computeConversion(scope, runtimeTimeType, compileTimeType);
	}

	public FieldBinding fieldBinding() {

		return binding;
	}

	public void generateAssignment(
		BlockScope currentScope,
		CodeStream codeStream,
		Assignment assignment,
		boolean valueRequired) {

		receiver.generateCode(
			currentScope,
			codeStream,
			!this.codegenBinding.isStatic());
		assignment.expression.generateCode(currentScope, codeStream, true);
		fieldStore(
			codeStream,
			this.codegenBinding,
			syntheticAccessors == null ? null : syntheticAccessors[WRITE],
			valueRequired);
		if (valueRequired) {
			codeStream.generateImplicitConversion(assignment.implicitConversion);
		}
		// no need for generic cast as value got dupped
	}

	/**
	 * Field reference code generation
	 *
	 * @param currentScope org.eclipse.wst.jsdt.internal.compiler.lookup.BlockScope
	 * @param codeStream org.eclipse.wst.jsdt.internal.compiler.codegen.CodeStream
	 * @param valueRequired boolean
	 */
	public void generateCode(
		BlockScope currentScope,
		CodeStream codeStream,
		boolean valueRequired) {

		int pc = codeStream.position;
		if (constant != NotAConstant) {
			if (valueRequired) {
				codeStream.generateConstant(constant, implicitConversion);
			}
		} else {
			boolean isStatic = this.codegenBinding.isStatic();
			receiver.generateCode(currentScope, codeStream, !isStatic);
			if (valueRequired) {
				if (!this.codegenBinding.isConstantValue()) {
					if (this.codegenBinding.declaringClass == null) { // array length
						codeStream.arraylength();
					} else {
						if (syntheticAccessors == null || syntheticAccessors[READ] == null) {
							if (isStatic) {
								codeStream.getstatic(this.codegenBinding);
							} else {
								codeStream.getfield(this.codegenBinding);
							}
						} else {
							codeStream.invokestatic(syntheticAccessors[READ]);
						}
					}
					codeStream.generateImplicitConversion(implicitConversion);
					if (this.genericCast != null) codeStream.checkcast(this.genericCast);			
				} else {
					if (!isStatic) {
						codeStream.invokeObjectGetClass(); // perform null check
						codeStream.pop();
					}
					codeStream.generateConstant(this.codegenBinding.constant(), implicitConversion);
				}
			} else {
				if (!isStatic){
					codeStream.invokeObjectGetClass(); // perform null check
					codeStream.pop();
				}
			}
		}
		codeStream.recordPositionsFrom(pc, this.sourceStart);
	}

	public void generateCompoundAssignment(
		BlockScope currentScope,
		CodeStream codeStream,
		Expression expression,
		int operator,
		int assignmentImplicitConversion,
		boolean valueRequired) {

		boolean isStatic;
		receiver.generateCode(
			currentScope,
			codeStream,
			!(isStatic = this.codegenBinding.isStatic()));
		if (isStatic) {
			if (syntheticAccessors == null || syntheticAccessors[READ] == null) {
				codeStream.getstatic(this.codegenBinding);
			} else {
				codeStream.invokestatic(syntheticAccessors[READ]);
			}
		} else {
			codeStream.dup();
			if (syntheticAccessors == null || syntheticAccessors[READ] == null) {
				codeStream.getfield(this.codegenBinding);
			} else {
				codeStream.invokestatic(syntheticAccessors[READ]);
			}
		}
		int operationTypeID;
		if ((operationTypeID = implicitConversion >> 4) == T_String) {
		    // no need for generic cast on previous #getfield since using Object string buffer methods.
			codeStream.generateStringConcatenationAppend(currentScope, null, expression);
		} else {
			// promote the array reference to the suitable operation type
			codeStream.generateImplicitConversion(implicitConversion);
			// generate the increment value (will by itself  be promoted to the operation value)
			if (expression == IntLiteral.One) { // prefix operation
				codeStream.generateConstant(expression.constant, implicitConversion);
			} else {
				expression.generateCode(currentScope, codeStream, true);
			}
			// perform the operation
			codeStream.sendOperator(operator, operationTypeID);
			// cast the value back to the array reference type
			codeStream.generateImplicitConversion(assignmentImplicitConversion);
		}
		fieldStore(
			codeStream,
			this.codegenBinding,
			syntheticAccessors == null ? null : syntheticAccessors[WRITE],
			valueRequired);
		// no need for generic cast as value got dupped
	}

	public void generatePostIncrement(
		BlockScope currentScope,
		CodeStream codeStream,
		CompoundAssignment postIncrement,
		boolean valueRequired) {

		boolean isStatic;
		receiver.generateCode(
			currentScope,
			codeStream,
			!(isStatic = this.codegenBinding.isStatic()));
		if (isStatic) {
			if (syntheticAccessors == null || syntheticAccessors[READ] == null) {
				codeStream.getstatic(this.codegenBinding);
			} else {
				codeStream.invokestatic(syntheticAccessors[READ]);
			}
		} else {
			codeStream.dup();
			if (syntheticAccessors == null || syntheticAccessors[READ] == null) {
				codeStream.getfield(this.codegenBinding);
			} else {
				codeStream.invokestatic(syntheticAccessors[READ]);
			}
		}
		if (valueRequired) {
			if (isStatic) {
				if ((this.codegenBinding.type == LongBinding)
					|| (this.codegenBinding.type == DoubleBinding)) {
					codeStream.dup2();
				} else {
					codeStream.dup();
				}
			} else { // Stack:  [owner][old field value]  ---> [old field value][owner][old field value]
				if ((this.codegenBinding.type == LongBinding)
					|| (this.codegenBinding.type == DoubleBinding)) {
					codeStream.dup2_x1();
				} else {
					codeStream.dup_x1();
				}
			}
		}
		codeStream.generateConstant(
			postIncrement.expression.constant,
			implicitConversion);
		codeStream.sendOperator(postIncrement.operator, this.codegenBinding.type.id);
		codeStream.generateImplicitConversion(
			postIncrement.assignmentImplicitConversion);
		fieldStore(codeStream, this.codegenBinding, syntheticAccessors == null ? null : syntheticAccessors[WRITE], false);
	}
	/**
	 * @see org.eclipse.wst.jsdt.internal.compiler.lookup.InvocationSite#genericTypeArguments()
	 */
	public TypeBinding[] genericTypeArguments() {
		return null;
	}
	public static final Constant getConstantFor(
		FieldBinding binding,
		Reference reference,
		boolean isImplicit,
		Scope referenceScope) {

		//propagation of the constant.

		//ref can be a FieldReference, a SingleNameReference or a QualifiedNameReference
		//indexInQualification may have a value greater than zero only for QualifiednameReference
		//if ref==null then indexInQualification==0 AND implicitReceiver == false. This case is a 
		//degenerated case where a fake reference field (null) 
		//is associted to a real FieldBinding in order 
		//to allow its constant computation using the regular path (in other words, find the fieldDeclaration
		//and proceed to its type resolution). As implicitReceiver is false, no error reporting
		//against ref will be used ==> no nullPointerException risk .... 

		//special treatment for langage-built-in  field (their declaring class is null)
		if (binding.declaringClass == null) {
			//currently only one field "length" : the constant computation is never done
			return NotAConstant;
		}
		if (!binding.isFinal()) {
			binding.setConstant(NotAConstant);
			return NotAConstant;
		}
		Constant fieldConstant = binding.constant();
		if (fieldConstant != null) {
			if (isImplicit || (reference instanceof QualifiedNameReference
					&& binding == ((QualifiedNameReference)reference).binding)) {
				return fieldConstant;
			}
			return NotAConstant;
		}

		//The field has not been yet type checked.
		//It also means that the field is not coming from a class that
		//has already been compiled. It can only be from a class within
		//compilation units to process. Thus the field is NOT from a BinaryTypeBinbing

		FieldBinding originalField = binding.original();
		SourceTypeBinding sourceType = (SourceTypeBinding) originalField.declaringClass;
		TypeDeclaration typeDecl = sourceType.scope.referenceContext;
		FieldDeclaration fieldDecl = typeDecl.declarationOf(originalField);

		fieldDecl.resolve(originalField.isStatic() //side effect on binding 
				? typeDecl.staticInitializerScope
				: typeDecl.initializerScope); 

		if (isImplicit || (reference instanceof QualifiedNameReference
				&& binding == ((QualifiedNameReference)reference).binding)) {
			return binding.constant();
		}
		return NotAConstant;
	}

	public boolean isSuperAccess() {

		return receiver.isSuper();
	}

	public boolean isTypeAccess() {

		return receiver != null && receiver.isTypeReference();
	}

	/*
	 * No need to emulate access to protected fields since not implicitly accessed
	 */
	public void manageSyntheticAccessIfNecessary(BlockScope currentScope, FlowInfo flowInfo, boolean isReadAccess) {

		if (!flowInfo.isReachable()) return;
		// if field from parameterized type got found, use the original field at codegen time
		this.codegenBinding = this.binding.original();
		
		if (binding.isPrivate()) {
			if ((currentScope.enclosingSourceType() != this.codegenBinding.declaringClass) && !binding.isConstantValue()) {
				if (syntheticAccessors == null)
					syntheticAccessors = new MethodBinding[2];
				syntheticAccessors[isReadAccess ? READ : WRITE] = 
					((SourceTypeBinding) this.codegenBinding.declaringClass).addSyntheticMethod(this.codegenBinding, isReadAccess);
				currentScope.problemReporter().needToEmulateFieldAccess(this.codegenBinding, this, isReadAccess);
				return;
			}

		} else if (receiver instanceof QualifiedSuperReference) { // qualified super

			// qualified super need emulation always
			SourceTypeBinding destinationType =
				(SourceTypeBinding) (((QualifiedSuperReference) receiver)
					.currentCompatibleType);
			if (syntheticAccessors == null)
				syntheticAccessors = new MethodBinding[2];
			syntheticAccessors[isReadAccess ? READ : WRITE] = destinationType.addSyntheticMethod(this.codegenBinding, isReadAccess);
			currentScope.problemReporter().needToEmulateFieldAccess(this.codegenBinding, this, isReadAccess);
			return;

		} else if (binding.isProtected()) {

			SourceTypeBinding enclosingSourceType;
			if (((bits & DepthMASK) != 0)
				&& binding.declaringClass.getPackage()
					!= (enclosingSourceType = currentScope.enclosingSourceType()).getPackage()) {

				SourceTypeBinding currentCompatibleType =
					(SourceTypeBinding) enclosingSourceType.enclosingTypeAt(
						(bits & DepthMASK) >> DepthSHIFT);
				if (syntheticAccessors == null)
					syntheticAccessors = new MethodBinding[2];
				syntheticAccessors[isReadAccess ? READ : WRITE] = currentCompatibleType.addSyntheticMethod(this.codegenBinding, isReadAccess);
				currentScope.problemReporter().needToEmulateFieldAccess(this.codegenBinding, this, isReadAccess);
				return;
			}
		}
		// if the binding declaring class is not visible, need special action
		// for runtime compatibility on 1.2 VMs : change the declaring class of the binding
		// NOTE: from target 1.2 on, field's declaring class is touched if any different from receiver type
		if (this.binding.declaringClass != this.receiverType
			&& !this.receiverType.isArrayType()
			&& this.binding.declaringClass != null // array.length
			&& !this.binding.isConstantValue()
			&& ((currentScope.environment().options.targetJDK >= ClassFileConstants.JDK1_2
				&& this.binding.declaringClass.id != T_Object)
			//no change for Object fields (in case there was)
				|| !this.codegenBinding.declaringClass.canBeSeenBy(currentScope))) {
			this.codegenBinding =
				currentScope.enclosingSourceType().getUpdatedFieldBinding(
					this.codegenBinding,
					(ReferenceBinding) this.receiverType.erasure());
		}
	}


	public StringBuffer printExpression(int indent, StringBuffer output) {

		return receiver.printExpression(0, output).append('.').append(token);
	}
	
	public TypeBinding resolveType(BlockScope scope) {

		// Answer the signature type of the field.
		// constants are propaged when the field is final
		// and initialized with a (compile time) constant 

		//always ignore receiver cast, since may affect constant pool reference
		boolean receiverCast = false;
		if (this.receiver instanceof CastExpression) {
			this.receiver.bits |= IgnoreNeedForCastCheckMASK; // will check later on
			receiverCast = true;
		}
		this.receiverType = receiver.resolveType(scope);
		if (this.receiverType == null) {
			constant = NotAConstant;
			return null;
		}
		if (receiverCast) {
			 // due to change of declaring class with receiver type, only identity cast should be notified
			if (((CastExpression)this.receiver).expression.resolvedType == this.receiverType) { 
						scope.problemReporter().unnecessaryCast((CastExpression)this.receiver);		
			}
		}		
		// the case receiverType.isArrayType and token = 'length' is handled by the scope API
		this.codegenBinding = this.binding = scope.getField(this.receiverType, token, this);
		if (!binding.isValidBinding()) {
			constant = NotAConstant;
			scope.problemReporter().invalidField(this, this.receiverType);
			return null;
		}
		this.receiver.computeConversion(scope, this.receiverType, this.receiverType);
		if (isFieldUseDeprecated(binding, scope, (this.bits & IsStrictlyAssignedMASK) !=0)) {
			scope.problemReporter().deprecatedField(binding, this);
		}
		boolean isImplicitThisRcv = receiver.isImplicitThis();
		constant = FieldReference.getConstantFor(binding, this, isImplicitThisRcv, scope);
		if (!isImplicitThisRcv) {
			constant = NotAConstant;
		}
		if (binding.isStatic()) {
			// static field accessed through receiver? legal but unoptimal (optional warning)
			if (!(isImplicitThisRcv
					|| (receiver instanceof NameReference 
						&& (((NameReference) receiver).bits & BindingIds.TYPE) != 0))) {
				scope.problemReporter().nonStaticAccessToStaticField(this, binding);
			}
			if (!isImplicitThisRcv && binding.declaringClass != receiverType) {
				scope.problemReporter().indirectAccessToStaticField(this, binding);
			}
		}
		return this.resolvedType = binding.type;
	}

	public void setActualReceiverType(ReferenceBinding receiverType) {
		// ignored
	}

	public void setDepth(int depth) {

		bits &= ~DepthMASK; // flush previous depth if any			
		if (depth > 0) {
			bits |= (depth & 0xFF) << DepthSHIFT; // encoded on 8 bits
		}
	}

	public void setFieldIndex(int index) {
		// ignored
	}

	public void traverse(ASTVisitor visitor, BlockScope scope) {

		if (visitor.visit(this, scope)) {
			receiver.traverse(visitor, scope);
		}
		visitor.endVisit(this, scope);
	}
}
