/*******************************************************************************
 * 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.ast.AbstractMethodDeclaration;
import net.sourceforge.phpdt.internal.compiler.ast.FieldDeclaration;

public class SyntheticAccessMethodBinding extends MethodBinding {

	public FieldBinding targetReadField; // read access to a field

	public FieldBinding targetWriteField; // write access to a field

	public MethodBinding targetMethod; // method or constructor

	public int accessType;

	public final static int FieldReadAccess = 1; // field read

	public final static int FieldWriteAccess = 2; // field write

	public final static int MethodAccess = 3; // normal method

	public final static int ConstructorAccess = 4; // constructor

	public final static int SuperMethodAccess = 5; // super method

	final static char[] AccessMethodPrefix = { 'a', 'c', 'c', 'e', 's', 's',
			'$' };

	public int sourceStart = 0; // start position of the matching declaration

	public int index; // used for sorting access methods in the class file

	public SyntheticAccessMethodBinding(FieldBinding targetField,
			boolean isReadAccess, ReferenceBinding declaringClass) {

		this.modifiers = AccDefault | AccStatic;// | AccSynthetic;
		SourceTypeBinding declaringSourceType = (SourceTypeBinding) declaringClass;
		SyntheticAccessMethodBinding[] knownAccessMethods = declaringSourceType
				.syntheticAccessMethods();
		int methodId = knownAccessMethods == null ? 0
				: knownAccessMethods.length;
		this.index = methodId;
		this.selector = CharOperation.concat(AccessMethodPrefix, String
				.valueOf(methodId).toCharArray());
		if (isReadAccess) {
			this.returnType = targetField.type;
			if (targetField.isStatic()) {
				this.parameters = NoParameters;
			} else {
				this.parameters = new TypeBinding[1];
				this.parameters[0] = declaringSourceType;
			}
			this.targetReadField = targetField;
			this.accessType = FieldReadAccess;
		} else {
			this.returnType = VoidBinding;
			if (targetField.isStatic()) {
				this.parameters = new TypeBinding[1];
				this.parameters[0] = targetField.type;
			} else {
				this.parameters = new TypeBinding[2];
				this.parameters[0] = declaringSourceType;
				this.parameters[1] = targetField.type;
			}
			this.targetWriteField = targetField;
			this.accessType = FieldWriteAccess;
		}
		this.thrownExceptions = NoExceptions;
		this.declaringClass = declaringSourceType;

		// check for method collision
		boolean needRename;
		do {
			check: {
				needRename = false;
				// check for collision with known methods
				MethodBinding[] methods = declaringSourceType.methods;
				for (int i = 0, length = methods.length; i < length; i++) {
					if (this.selector == methods[i].selector
							&& this.areParametersEqual(methods[i])) {
						needRename = true;
						break check;
					}
				}
				// check for collision with synthetic accessors
				if (knownAccessMethods != null) {
					for (int i = 0, length = knownAccessMethods.length; i < length; i++) {
						if (knownAccessMethods[i] == null)
							continue;
						if (this.selector == knownAccessMethods[i].selector
								&& this.areParametersEqual(methods[i])) {
							needRename = true;
							break check;
						}
					}
				}
			}
			if (needRename) { // retry with a selector postfixed by a growing
								// methodId
				this.selector(CharOperation.concat(AccessMethodPrefix, String
						.valueOf(++methodId).toCharArray()));
			}
		} while (needRename);

		// retrieve sourceStart position for the target field for line number
		// attributes
		FieldDeclaration[] fieldDecls = declaringSourceType.scope.referenceContext.fields;
		if (fieldDecls != null) {
			for (int i = 0, max = fieldDecls.length; i < max; i++) {
				if (fieldDecls[i].binding == targetField) {
					this.sourceStart = fieldDecls[i].sourceStart;
					return;
				}
			}
		}

		/*
		 * did not find the target field declaration - it is a synthetic one
		 * public class A { public class B { public class C { void foo() {
		 * System.out.println("A.this = " + A.this); } } } public static void
		 * main(String args[]) { new A().new B().new C().foo(); } }
		 */
		// We now at this point - per construction - it is for sure an enclosing
		// instance, we are going to
		// show the target field type declaration location.
		this.sourceStart = declaringSourceType.scope.referenceContext.sourceStart; // use
																					// the
																					// target
																					// declaring
																					// class
																					// name
																					// position
																					// instead
	}

	public SyntheticAccessMethodBinding(MethodBinding targetMethod,
			boolean isSuperAccess, ReferenceBinding receiverType) {

		if (targetMethod.isConstructor()) {
			this.initializeConstructorAccessor(targetMethod);
		} else {
			this.initializeMethodAccessor(targetMethod, isSuperAccess,
					receiverType);
		}
	}

	/**
	 * An constructor accessor is a constructor with an extra argument
	 * (declaringClass), in case of collision with an existing constructor, then
	 * add again an extra argument (declaringClass again).
	 */
	public void initializeConstructorAccessor(MethodBinding targetConstructor) {

		this.targetMethod = targetConstructor;
		this.modifiers = AccDefault;// | AccSynthetic;
		SourceTypeBinding sourceType = (SourceTypeBinding) targetConstructor.declaringClass;
		SyntheticAccessMethodBinding[] knownAccessMethods = sourceType
				.syntheticAccessMethods();
		this.index = knownAccessMethods == null ? 0 : knownAccessMethods.length;

		this.selector = targetConstructor.selector;
		this.returnType = targetConstructor.returnType;
		this.accessType = ConstructorAccess;
		this.parameters = new TypeBinding[targetConstructor.parameters.length + 1];
		System.arraycopy(targetConstructor.parameters, 0, this.parameters, 0,
				targetConstructor.parameters.length);
		parameters[targetConstructor.parameters.length] = targetConstructor.declaringClass;
		this.thrownExceptions = targetConstructor.thrownExceptions;
		this.declaringClass = sourceType;

		// check for method collision
		boolean needRename;
		do {
			check: {
				needRename = false;
				// check for collision with known methods
				MethodBinding[] methods = sourceType.methods;
				for (int i = 0, length = methods.length; i < length; i++) {
					if (this.selector == methods[i].selector
							&& this.areParametersEqual(methods[i])) {
						needRename = true;
						break check;
					}
				}
				// check for collision with synthetic accessors
				if (knownAccessMethods != null) {
					for (int i = 0, length = knownAccessMethods.length; i < length; i++) {
						if (knownAccessMethods[i] == null)
							continue;
						if (this.selector == knownAccessMethods[i].selector
								&& this
										.areParametersEqual(knownAccessMethods[i])) {
							needRename = true;
							break check;
						}
					}
				}
			}
			if (needRename) { // retry with a new extra argument
				int length = this.parameters.length;
				System.arraycopy(this.parameters, 0,
						this.parameters = new TypeBinding[length + 1], 0,
						length);
				this.parameters[length] = this.declaringClass;
			}
		} while (needRename);

		// retrieve sourceStart position for the target method for line number
		// attributes
		AbstractMethodDeclaration[] methodDecls = sourceType.scope.referenceContext.methods;
		if (methodDecls != null) {
			for (int i = 0, length = methodDecls.length; i < length; i++) {
				if (methodDecls[i].binding == targetConstructor) {
					this.sourceStart = methodDecls[i].sourceStart;
					return;
				}
			}
		}
	}

	/**
	 * An method accessor is a method with an access$N selector, where N is
	 * incremented in case of collisions.
	 */
	public void initializeMethodAccessor(MethodBinding targetMethod,
			boolean isSuperAccess, ReferenceBinding declaringClass) {

		this.targetMethod = targetMethod;
		this.modifiers = AccDefault | AccStatic;// | AccSynthetic;
		SourceTypeBinding declaringSourceType = (SourceTypeBinding) declaringClass;
		SyntheticAccessMethodBinding[] knownAccessMethods = declaringSourceType
				.syntheticAccessMethods();
		int methodId = knownAccessMethods == null ? 0
				: knownAccessMethods.length;
		this.index = methodId;

		this.selector = CharOperation.concat(AccessMethodPrefix, String
				.valueOf(methodId).toCharArray());
		this.returnType = targetMethod.returnType;
		this.accessType = isSuperAccess ? SuperMethodAccess : MethodAccess;

		if (targetMethod.isStatic()) {
			this.parameters = targetMethod.parameters;
		} else {
			this.parameters = new TypeBinding[targetMethod.parameters.length + 1];
			this.parameters[0] = declaringSourceType;
			System.arraycopy(targetMethod.parameters, 0, this.parameters, 1,
					targetMethod.parameters.length);
		}
		this.thrownExceptions = targetMethod.thrownExceptions;
		this.declaringClass = declaringSourceType;

		// check for method collision
		boolean needRename;
		do {
			check: {
				needRename = false;
				// check for collision with known methods
				MethodBinding[] methods = declaringSourceType.methods;
				for (int i = 0, length = methods.length; i < length; i++) {
					if (this.selector == methods[i].selector
							&& this.areParametersEqual(methods[i])) {
						needRename = true;
						break check;
					}
				}
				// check for collision with synthetic accessors
				if (knownAccessMethods != null) {
					for (int i = 0, length = knownAccessMethods.length; i < length; i++) {
						if (knownAccessMethods[i] == null)
							continue;
						if (this.selector == knownAccessMethods[i].selector
								&& this
										.areParametersEqual(knownAccessMethods[i])) {
							needRename = true;
							break check;
						}
					}
				}
			}
			if (needRename) { // retry with a selector & a growing methodId
				this.selector(CharOperation.concat(AccessMethodPrefix, String
						.valueOf(++methodId).toCharArray()));
			}
		} while (needRename);

		// retrieve sourceStart position for the target method for line number
		// attributes
		AbstractMethodDeclaration[] methodDecls = declaringSourceType.scope.referenceContext.methods;
		if (methodDecls != null) {
			for (int i = 0, length = methodDecls.length; i < length; i++) {
				if (methodDecls[i].binding == targetMethod) {
					this.sourceStart = methodDecls[i].sourceStart;
					return;
				}
			}
		}
	}

	protected boolean isConstructorRelated() {
		return accessType == ConstructorAccess;
	}
}
