/*******************************************************************************
 * 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.codegen.*;
import org.eclipse.wst.jsdt.internal.compiler.flow.*;
import org.eclipse.wst.jsdt.internal.compiler.lookup.*;

public class FieldDeclaration extends AbstractVariableDeclaration {
	public FieldBinding binding;
	boolean hasBeenResolved = false;
	public Javadoc javadoc;

	//allows to retrieve both the "type" part of the declaration (part1)
	//and also the part that decribe the name and the init and optionally
	//some other dimension ! .... 
	//public int[] a, b[] = X, c ;
	//for b that would give for 
	// - part1 : public int[]
	// - part2 : b[] = X,

	public int endPart1Position;
	public int endPart2Position;

	public FieldDeclaration() {
		// for subtypes or conversion
	}

	public FieldDeclaration(
		char[] name,
		int sourceStart,
		int sourceEnd) {

		this.name = name;

		//due to some declaration like 
		// int x, y = 3, z , x ;
		//the sourceStart and the sourceEnd is ONLY on  the name
		this.sourceStart = sourceStart;
		this.sourceEnd = sourceEnd;
	}

	public FlowInfo analyseCode(
		MethodScope initializationScope,
		FlowContext flowContext,
		FlowInfo flowInfo) {

		if (this.binding != null && this.binding.isPrivate() && !this.binding.isPrivateUsed()) {
			if (!initializationScope.referenceCompilationUnit().compilationResult.hasSyntaxError()) {
				initializationScope.problemReporter().unusedPrivateField(this);
			}
		}
		// cannot define static non-constant field inside nested class
		if (this.binding != null
			&& this.binding.isValidBinding()
			&& this.binding.isStatic()
			&& !this.binding.isConstantValue()
			&& this.binding.declaringClass.isNestedType()
			&& this.binding.declaringClass.isClass()
			&& !this.binding.declaringClass.isStatic()) {
			initializationScope.problemReporter().unexpectedStaticModifierForField(
				(SourceTypeBinding) this.binding.declaringClass,
				this);
		}

		if (this.initialization != null) {
			flowInfo =
				this.initialization
					.analyseCode(initializationScope, flowContext, flowInfo)
					.unconditionalInits();
			flowInfo.markAsDefinitelyAssigned(this.binding);
		}
		return flowInfo;
	}

	/**
	 * Code generation for a field declaration:
	 *	   standard assignment to a field 
	 *
	 * @param currentScope org.eclipse.wst.jsdt.internal.compiler.lookup.BlockScope
	 * @param codeStream org.eclipse.wst.jsdt.internal.compiler.codegen.CodeStream
	 */
	public void generateCode(BlockScope currentScope, CodeStream codeStream) {

		if ((this.bits & IsReachableMASK) == 0) {
			return;
		}
		// do not generate initialization code if final and static (constant is then
		// recorded inside the field itself).
		int pc = codeStream.position;
		boolean isStatic;
		if (this.initialization != null
			&& !((isStatic = this.binding.isStatic()) && this.binding.isConstantValue())) {
			// non-static field, need receiver
			if (!isStatic)
				codeStream.aload_0();
			// generate initialization value
			this.initialization.generateCode(currentScope, codeStream, true);
			// store into field
			if (isStatic) {
				codeStream.putstatic(this.binding);
			} else {
				codeStream.putfield(this.binding);
			}
		}
		codeStream.recordPositionsFrom(pc, this.sourceStart);
	}

	public boolean isField() {

		return true;
	}

	public boolean isStatic() {

		if (this.binding != null)
			return this.binding.isStatic();
		return (this.modifiers & AccStatic) != 0;
	}
	
	public void resolve(MethodScope initializationScope) {

		// the two <constant = Constant.NotAConstant> could be regrouped into
		// a single line but it is clearer to have two lines while the reason of their
		// existence is not at all the same. See comment for the second one.

		//--------------------------------------------------------
		if (!this.hasBeenResolved && this.binding != null && this.binding.isValidBinding()) {

			this.hasBeenResolved = true;

			// check if field is hiding some variable - issue is that field binding already got inserted in scope
			// thus must lookup separately in super type and outer context
			ClassScope classScope = initializationScope.enclosingClassScope();
			
			if (classScope != null) {
				SourceTypeBinding declaringType = classScope.enclosingSourceType();
				boolean checkLocal = true;
				if (declaringType.superclass != null) {
					Binding existingVariable = classScope.findField(declaringType.superclass, this.name, this,  false /*do not resolve hidden field*/);
					if (existingVariable != null && existingVariable.isValidBinding()){
						initializationScope.problemReporter().fieldHiding(this, existingVariable);
						checkLocal = false; // already found a matching field
					}
				}
				if (checkLocal) {
					Scope outerScope = classScope.parent;
					// only corner case is: lookup of outer field through static declaringType, which isn't detected by #getBinding as lookup starts
					// from outer scope. Subsequent static contexts are detected for free.
					Binding existingVariable = outerScope.getBinding(this.name, BindingIds.VARIABLE, this, false /*do not resolve hidden field*/);
					if (existingVariable != null && existingVariable.isValidBinding()
							&& (!(existingVariable instanceof FieldBinding)
									|| ((FieldBinding) existingVariable).isStatic() 
									|| !declaringType.isStatic())) {
						initializationScope.problemReporter().fieldHiding(this, existingVariable);
					}
				}
			}
			
			this.type.resolvedType = this.binding.type; // update binding for type reference

			FieldBinding previousField = initializationScope.initializedField;
			int previousFieldID = initializationScope.lastVisibleFieldID;
			try {
				initializationScope.initializedField = this.binding;
				initializationScope.lastVisibleFieldID = this.binding.id;

				// the resolution of the initialization hasn't been done
				if (this.initialization == null) {
					this.binding.setConstant(Constant.NotAConstant);
				} else {
					// break dead-lock cycles by forcing constant to NotAConstant
					this.binding.setConstant(Constant.NotAConstant);
					
					TypeBinding fieldType = this.binding.type;
					TypeBinding initializationType;
					this.initialization.setExpectedType(fieldType); // needed in case of generic method invocation
					if (this.initialization instanceof ArrayInitializer) {

						if ((initializationType = this.initialization.resolveTypeExpecting(initializationScope, fieldType)) != null) {
							((ArrayInitializer) this.initialization).binding = (ArrayBinding) initializationType;
							this.initialization.computeConversion(initializationScope, fieldType, initializationType);
						}
					} else if ((initializationType = this.initialization.resolveType(initializationScope)) != null) {

						if (this.initialization.isConstantValueOfTypeAssignableToType(initializationType, fieldType)
								|| (fieldType.isBaseType() && BaseTypeBinding.isWidening(fieldType.id, initializationType.id))
								|| initializationType.isCompatibleWith(fieldType)) {
							this.initialization.computeConversion(initializationScope, fieldType, initializationType);
							if (initializationType.isRawType() && (fieldType.isBoundParameterizedType() || fieldType.isGenericType())) {
								    initializationScope.problemReporter().unsafeRawConversion(this.initialization, initializationType, fieldType);
							}									
						} else {
							initializationScope.problemReporter().typeMismatchError(initializationType, fieldType, this);
						}
						if (this.binding.isFinal()){ // cast from constant actual type to variable type
							this.binding.setConstant(this.initialization.constant.castTo((this.binding.type.id << 4) + this.initialization.constant.typeID()));
						}
					} else {
						this.binding.setConstant(NotAConstant);
					}
				}
				// Resolve Javadoc comment if one is present
				if (this.javadoc != null) {
					/*
					if (classScope != null) {
						this.javadoc.resolve(classScope);
					}
					*/
					this.javadoc.resolve(initializationScope);
				} else if (this.binding != null && this.binding.declaringClass != null && !this.binding.declaringClass.isLocalType()) {
					initializationScope.problemReporter().javadocMissing(this.sourceStart, this.sourceEnd, this.binding.modifiers);
				}
			} finally {
				initializationScope.initializedField = previousField;
				initializationScope.lastVisibleFieldID = previousFieldID;
				if (this.binding.constant() == null)
					this.binding.setConstant(Constant.NotAConstant);
			}
		}
	}

	public void traverse(ASTVisitor visitor, MethodScope scope) {

		if (visitor.visit(this, scope)) {
			this.type.traverse(visitor, scope);
			if (this.initialization != null)
				this.initialization.traverse(visitor, scope);
		}
		visitor.endVisit(this, scope);
	}
}
