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

import net.sourceforge.phpdt.internal.compiler.ASTVisitor;
import net.sourceforge.phpdt.internal.compiler.flow.FlowContext;
import net.sourceforge.phpdt.internal.compiler.flow.FlowInfo;
import net.sourceforge.phpdt.internal.compiler.lookup.BlockScope;
import net.sourceforge.phpdt.internal.compiler.lookup.InvocationSite;
import net.sourceforge.phpdt.internal.compiler.lookup.LocalTypeBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.LocalVariableBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.MethodBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.NestedTypeBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.ReferenceBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.SourceTypeBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.SyntheticArgumentBinding;
import net.sourceforge.phpdt.internal.compiler.lookup.TypeBinding;

public class AllocationExpression extends Expression implements InvocationSite {

	public TypeReference type;

	public Expression[] arguments;

	public MethodBinding binding;

	MethodBinding syntheticAccessor;

	public AllocationExpression() {
	}

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

		// check captured variables are initialized in current context (26134)
		checkCapturedLocalInitializationIfNecessary(
				this.binding.declaringClass, currentScope, flowInfo);

		// process arguments
		if (arguments != null) {
			for (int i = 0, count = arguments.length; i < count; i++) {
				flowInfo = arguments[i].analyseCode(currentScope, flowContext,
						flowInfo).unconditionalInits();
			}
		}
		// record some dependency information for exception types
		ReferenceBinding[] thrownExceptions;
		if (((thrownExceptions = this.binding.thrownExceptions).length) != 0) {
			// check exception handling
			flowContext.checkExceptionHandlers(thrownExceptions, this,
					flowInfo, currentScope);
		}
		manageEnclosingInstanceAccessIfNecessary(currentScope);
		manageSyntheticAccessIfNecessary(currentScope);

		return flowInfo;
	}

	public void checkCapturedLocalInitializationIfNecessary(
			ReferenceBinding checkedType, BlockScope currentScope,
			FlowInfo flowInfo) {

		if (checkedType.isLocalType() && !checkedType.isAnonymousType()
				&& !currentScope.isDefinedInType(checkedType)) { // only
																	// check
																	// external
																	// allocations
			NestedTypeBinding nestedType = (NestedTypeBinding) checkedType;
			SyntheticArgumentBinding[] syntheticArguments = nestedType
					.syntheticOuterLocalVariables();
			if (syntheticArguments != null)
				for (int i = 0, count = syntheticArguments.length; i < count; i++) {
					SyntheticArgumentBinding syntheticArgument = syntheticArguments[i];
					LocalVariableBinding targetLocal;
					if ((targetLocal = syntheticArgument.actualOuterLocalVariable) == null)
						continue;
					// if (targetLocal.declaration != null &&
					// !flowInfo.isDefinitelyAssigned(targetLocal)){
					// currentScope.problemReporter().uninitializedLocalVariable(targetLocal,
					// this);
					// }
				}

		}
	}

	public Expression enclosingInstance() {
		return null;
	}

	// public void generateCode(
	// BlockScope currentScope,
	// CodeStream codeStream,
	// boolean valueRequired) {
	//
	// int pc = codeStream.position;
	// ReferenceBinding allocatedType = binding.declaringClass;
	//
	// codeStream.new_(allocatedType);
	// if (valueRequired) {
	// codeStream.dup();
	// }
	// // better highlight for allocation: display the type individually
	// codeStream.recordPositionsFrom(pc, type.sourceStart);
	//
	// // handling innerclass instance allocation - enclosing instance arguments
	// if (allocatedType.isNestedType()) {
	// codeStream.generateSyntheticEnclosingInstanceValues(
	// currentScope,
	// allocatedType,
	// enclosingInstance(),
	// this);
	// }
	// // generate the arguments for constructor
	// if (arguments != null) {
	// for (int i = 0, count = arguments.length; i < count; i++) {
	// arguments[i].generateCode(currentScope, codeStream, true);
	// }
	// }
	// // handling innerclass instance allocation - outer local arguments
	// if (allocatedType.isNestedType()) {
	// codeStream.generateSyntheticOuterArgumentValues(
	// currentScope,
	// allocatedType,
	// this);
	// }
	// // invoke constructor
	// if (syntheticAccessor == null) {
	// codeStream.invokespecial(binding);
	// } else {
	// // synthetic accessor got some extra arguments appended to its signature,
	// which need values
	// for (int i = 0,
	// max = syntheticAccessor.parameters.length - binding.parameters.length;
	// i < max;
	// i++) {
	// codeStream.aconst_null();
	// }
	// codeStream.invokespecial(syntheticAccessor);
	// }
	// codeStream.recordPositionsFrom(pc, this.sourceStart);
	// }

	public boolean isSuperAccess() {

		return false;
	}

	public boolean isTypeAccess() {

		return true;
	}

	/*
	 * Inner emulation consists in either recording a dependency link only, or
	 * performing one level of propagation.
	 * 
	 * Dependency mechanism is used whenever dealing with source target types,
	 * since by the time we reach them, we might not yet know their exact need.
	 */
	public void manageEnclosingInstanceAccessIfNecessary(BlockScope currentScope) {

		ReferenceBinding allocatedType;

		// perform some emulation work in case there is some and we are inside a
		// local type only
		if ((allocatedType = binding.declaringClass).isNestedType()
				&& currentScope.enclosingSourceType().isLocalType()) {

			if (allocatedType.isLocalType()) {
				((LocalTypeBinding) allocatedType).addInnerEmulationDependent(
						currentScope, false);
				// request cascade of accesses
			} else {
				// locally propagate, since we already now the desired shape for
				// sure
				currentScope.propagateInnerEmulation(allocatedType, false);
				// request cascade of accesses
			}
		}
	}

	public void manageSyntheticAccessIfNecessary(BlockScope currentScope) {

		if (binding.isPrivate()
				&& (currentScope.enclosingSourceType() != binding.declaringClass)) {

			// if (currentScope
			// .environment()
			// .options
			// .isPrivateConstructorAccessChangingVisibility) {
			// binding.tagForClearingPrivateModifier();
			// // constructor will not be dumped as private, no emulation
			// required thus
			// } else {
			syntheticAccessor = ((SourceTypeBinding) binding.declaringClass)
					.addSyntheticMethod(binding, isSuperAccess());
			currentScope.problemReporter().needToEmulateMethodAccess(binding,
					this);
			// }
		}
	}

	public StringBuffer printExpression(int indent, StringBuffer output) {

		output.append("new "); //$NON-NLS-1$
		type.printExpression(0, output);
		output.append('(');
		if (arguments != null) {
			for (int i = 0; i < arguments.length; i++) {
				if (i > 0)
					output.append(", "); //$NON-NLS-1$
				arguments[i].printExpression(0, output);
			}
		}
		return output.append(')');
	}

	public TypeBinding resolveType(BlockScope scope) {

		// Propagate the type checking to the arguments, and check if the
		// constructor is defined.
		constant = NotAConstant;
		this.resolvedType = type.resolveType(scope);
		// will check for null after args are resolved

		// buffering the arguments' types
		TypeBinding[] argumentTypes = NoParameters;
		if (arguments != null) {
			boolean argHasError = false;
			int length = arguments.length;
			argumentTypes = new TypeBinding[length];
			for (int i = 0; i < length; i++)
				if ((argumentTypes[i] = arguments[i].resolveType(scope)) == null)
					argHasError = true;
			if (argHasError)
				return this.resolvedType;
		}
		if (this.resolvedType == null)
			return null;

		if (!this.resolvedType.canBeInstantiated()) {
			scope.problemReporter().cannotInstantiate(type, this.resolvedType);
			return this.resolvedType;
		}
		ReferenceBinding allocatedType = (ReferenceBinding) this.resolvedType;
		if (!(binding = scope
				.getConstructor(allocatedType, argumentTypes, this))
				.isValidBinding()) {
			if (binding.declaringClass == null)
				binding.declaringClass = allocatedType;
			scope.problemReporter().invalidConstructor(this, binding);
			return this.resolvedType;
		}
		if (isMethodUseDeprecated(binding, scope))
			scope.problemReporter().deprecatedMethod(binding, this);

		if (arguments != null)
			for (int i = 0; i < arguments.length; i++)
				arguments[i].implicitWidening(binding.parameters[i],
						argumentTypes[i]);
		return allocatedType;
	}

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

	public void setDepth(int i) {
		// ignored
	}

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

	public String toStringExpression() {

		String s = "new " + type.toString(0); //$NON-NLS-1$
		if (arguments == null)
			s = s + "()"; //$NON-NLS-1$
		else {
			s = s + "("; //$NON-NLS-1$
			for (int i = 0; i < arguments.length; i++) {
				s = s + arguments[i].toStringExpression();
				if (i == (arguments.length - 1))
					s = s + ")"; //$NON-NLS-1$
				else
					s = s + ", "; //$NON-NLS-1$
			}
		}
		return s;
	}

	public void traverse(ASTVisitor visitor, BlockScope scope) {

		if (visitor.visit(this, scope)) {
			int argumentsLength;
			type.traverse(visitor, scope);
			if (arguments != null) {
				argumentsLength = arguments.length;
				for (int i = 0; i < argumentsLength; i++)
					arguments[i].traverse(visitor, scope);
			}
		}
		visitor.endVisit(this, scope);
	}
}
