/*******************************************************************************
 * 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.jdt.internal.compiler.parser;

/**
 * Converter from source element type to parsed compilation unit.
 *
 * Limitation:
 * | The source element field does not carry any information for its constant part, thus
 * | the converted parse tree will not include any field initializations.
 * | Therefore, any binary produced by compiling against converted source elements will
 * | not take advantage of remote field constant inlining.
 * | Given the intended purpose of the conversion is to resolve references, this is not
 * | a problem.
 *
 */

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.env.ISourceField;
import org.eclipse.jdt.internal.compiler.env.ISourceImport;
import org.eclipse.jdt.internal.compiler.env.ISourceMethod;
import org.eclipse.jdt.internal.compiler.env.ISourceType;

import org.eclipse.jdt.internal.compiler.lookup.CompilerModifiers;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;

public class SourceTypeConverter implements CompilerModifiers {
	
	public static final int FIELD = 0x01;
	public static final int CONSTRUCTOR = 0x02;
	public static final int METHOD = 0x04;
	public static final int MEMBER_TYPE = 0x08;
	public static final int FIELD_INITIALIZATION = 0x10;
	public static final int FIELD_AND_METHOD = FIELD | CONSTRUCTOR | METHOD;
	public static final int NONE = 0;
	
	private int flags;
	private CompilationUnitDeclaration unit;
	private Parser parser;
	private ProblemReporter problemReporter;
	
	private SourceTypeConverter(int flags, ProblemReporter problemReporter) {
		this.flags = flags;
		this.problemReporter = problemReporter;
	}

	/*
	 * Convert a set of source element types into a parsed compilation unit declaration
	 * The argument types are then all grouped in the same unit. The argument types must 
	 * at least contain one type.
	 * Can optionally ignore fields & methods or member types or field initialization
	 */
	public static CompilationUnitDeclaration buildCompilationUnit(
		ISourceType[] sourceTypes,
		int flags,
		ProblemReporter problemReporter,
		CompilationResult compilationResult) {
			
		return 
			new SourceTypeConverter(flags, problemReporter).convert(sourceTypes, compilationResult);
	}

	/*
	 * Convert a set of source element types into a parsed compilation unit declaration
	 * The argument types are then all grouped in the same unit. The argument types must 
	 * at least contain one type.
	 */
	private CompilationUnitDeclaration convert(ISourceType[] sourceTypes, CompilationResult compilationResult) {
		this.unit = new CompilationUnitDeclaration(this.problemReporter, compilationResult, 0);
		// not filled at this point

		if (sourceTypes.length == 0) return this.unit;
		ISourceType sourceType = sourceTypes[0];
		if (sourceType.getName() == null)
		    // TODO (jerome) investigate when this can happen : if this can happen, fix clients to protect themselves
			return null; // do a basic test that the sourceType is valid

		/* only positions available */
		int start = sourceType.getNameSourceStart();
		int end = sourceType.getNameSourceEnd();

		/* convert package and imports */
		if (sourceType.getPackageName() != null
			&& sourceType.getPackageName().length > 0)
			// if its null then it is defined in the default package
			this.unit.currentPackage =
				createImportReference(sourceType.getPackageName(), start, end, false, AccDefault);
		ISourceImport[]  sourceImports = sourceType.getImports();
		int importCount = sourceImports == null ? 0 : sourceImports.length;
		this.unit.imports = new ImportReference[importCount];
		for (int i = 0; i < importCount; i++) {
			ISourceImport sourceImport = sourceImports[i];
			this.unit.imports[i] = createImportReference(
				sourceImport.getName(), 
				sourceImport.getDeclarationSourceStart(),
				sourceImport.getDeclarationSourceEnd(),
				sourceImport.onDemand(),
				sourceImport.getModifiers());
		}
		/* convert type(s) */
		int typeCount = sourceTypes.length;
		this.unit.types = new TypeDeclaration[typeCount];
		for (int i = 0; i < typeCount; i++) {
			this.unit.types[i] =
				convert(sourceTypes[i], compilationResult);
		}
		return this.unit;
	}
	
	/*
	 * Convert a field source element into a parsed field declaration
	 */
	private FieldDeclaration convert(ISourceField sourceField, TypeDeclaration type) {

		FieldDeclaration field = new FieldDeclaration();

		int start = sourceField.getNameSourceStart();
		int end = sourceField.getNameSourceEnd();

		field.name = sourceField.getName();
		field.sourceStart = start;
		field.sourceEnd = end;
		field.type = createTypeReference(sourceField.getTypeName(), start, end);
		field.declarationSourceStart = sourceField.getDeclarationSourceStart();
		field.declarationSourceEnd = sourceField.getDeclarationSourceEnd();
		field.modifiers = sourceField.getModifiers();

		if ((this.flags & FIELD_INITIALIZATION) != 0) {
			/* conversion of field constant */
			char[] initializationSource = sourceField.getInitializationSource();
			if (initializationSource != null) {
				if (this.parser == null) {
					this.parser = new Parser(this.problemReporter, true);
				}
				this.parser.parse(field, type, this.unit, initializationSource);
			}
		}
		
		return field;
	}

	/*
	 * Convert a method source element into a parsed method/constructor declaration 
	 */
	private AbstractMethodDeclaration convert(ISourceMethod sourceMethod, CompilationResult compilationResult) {

		AbstractMethodDeclaration method;

		/* only source positions available */
		int start = sourceMethod.getNameSourceStart();
		int end = sourceMethod.getNameSourceEnd();

		if (sourceMethod.isConstructor()) {
			ConstructorDeclaration decl = new ConstructorDeclaration(compilationResult);
			decl.isDefaultConstructor = false;
			method = decl;
		} else {
			MethodDeclaration decl = new MethodDeclaration(compilationResult);
			/* convert return type */
			decl.returnType =
				createTypeReference(sourceMethod.getReturnTypeName(), start, end);
			method = decl;
		}
		method.selector = sourceMethod.getSelector();
		method.modifiers = sourceMethod.getModifiers();
		method.sourceStart = start;
		method.sourceEnd = end;
		method.declarationSourceStart = sourceMethod.getDeclarationSourceStart();
		method.declarationSourceEnd = sourceMethod.getDeclarationSourceEnd();

		/* convert arguments */
		char[][] argumentTypeNames = sourceMethod.getArgumentTypeNames();
		char[][] argumentNames = sourceMethod.getArgumentNames();
		int argumentCount = argumentTypeNames == null ? 0 : argumentTypeNames.length;
		long position = (long) start << 32 + end;
		method.arguments = new Argument[argumentCount];
		for (int i = 0; i < argumentCount; i++) {
			method.arguments[i] =
				new Argument(
					argumentNames[i],
					position,
					createTypeReference(argumentTypeNames[i], start, end),
					AccDefault);
			// do not care whether was final or not
		}

		/* convert thrown exceptions */
		char[][] exceptionTypeNames = sourceMethod.getExceptionTypeNames();
		int exceptionCount = exceptionTypeNames == null ? 0 : exceptionTypeNames.length;
		method.thrownExceptions = new TypeReference[exceptionCount];
		for (int i = 0; i < exceptionCount; i++) {
			method.thrownExceptions[i] =
				createTypeReference(exceptionTypeNames[i], start, end);
		}
		return method;
	}

	/*
	 * Convert a source element type into a parsed type declaration
	 */
	private TypeDeclaration convert(ISourceType sourceType, CompilationResult compilationResult) {
		/* create type declaration - can be member type */
		TypeDeclaration type = new TypeDeclaration(compilationResult);
		if (sourceType.getEnclosingType() != null) {
			type.bits |= ASTNode.IsMemberTypeMASK;
		}
		type.name = sourceType.getName();
		int start, end; // only positions available
		type.sourceStart = start = sourceType.getNameSourceStart();
		type.sourceEnd = end = sourceType.getNameSourceEnd();
		type.modifiers = sourceType.getModifiers();
		type.declarationSourceStart = sourceType.getDeclarationSourceStart();
		type.declarationSourceEnd = sourceType.getDeclarationSourceEnd();
		type.bodyEnd = type.declarationSourceEnd;

		/* set superclass and superinterfaces */
		if (sourceType.getSuperclassName() != null)
			type.superclass =
				createTypeReference(sourceType.getSuperclassName(), start, end);
		char[][] interfaceNames = sourceType.getInterfaceNames();
		int interfaceCount = interfaceNames == null ? 0 : interfaceNames.length;
		type.superInterfaces = new TypeReference[interfaceCount];
		for (int i = 0; i < interfaceCount; i++) {
			type.superInterfaces[i] = createTypeReference(interfaceNames[i], start, end);
		}
		/* convert member types */
		if ((this.flags & MEMBER_TYPE) != 0) {
			ISourceType[] sourceMemberTypes = sourceType.getMemberTypes();
			int sourceMemberTypeCount =
				sourceMemberTypes == null ? 0 : sourceMemberTypes.length;
			type.memberTypes = new TypeDeclaration[sourceMemberTypeCount];
			for (int i = 0; i < sourceMemberTypeCount; i++) {
				type.memberTypes[i] = convert(sourceMemberTypes[i], compilationResult);
			}
		}

		/* convert fields */
		if ((this.flags & FIELD) != 0) {
			ISourceField[] sourceFields = sourceType.getFields();
			int sourceFieldCount = sourceFields == null ? 0 : sourceFields.length;
			type.fields = new FieldDeclaration[sourceFieldCount];
			for (int i = 0; i < sourceFieldCount; i++) {
				type.fields[i] = convert(sourceFields[i], type);
			}
		}

		/* convert methods - need to add default constructor if necessary */
		boolean needConstructor = (this.flags & CONSTRUCTOR) != 0;
		boolean needMethod = (this.flags & METHOD) != 0;
		if (needConstructor || needMethod) {
			
			ISourceMethod[] sourceMethods = sourceType.getMethods();
			int sourceMethodCount = sourceMethods == null ? 0 : sourceMethods.length;
	
			/* source type has a constructor ?           */
			/* by default, we assume that one is needed. */
			int extraConstructor = 0;
			int methodCount = 0;
			boolean isInterface = type.isInterface();
			if (!isInterface) {
				extraConstructor = needConstructor ? 1 : 0;
				for (int i = 0; i < sourceMethodCount; i++) {
					if (sourceMethods[i].isConstructor()) {
						if (needConstructor) {
							extraConstructor = 0; // Does not need the extra constructor since one constructor already exists.
							methodCount++;
						}
					} else if (needMethod) {
						methodCount++;
					}
				}
			} else {
				methodCount = needMethod ? sourceMethodCount : 0;
			}
			type.methods = new AbstractMethodDeclaration[methodCount + extraConstructor];
			if (extraConstructor != 0) { // add default constructor in first position
				type.methods[0] = type.createsInternalConstructor(false, false);
			}
			int index = 0;
			for (int i = 0; i < sourceMethodCount; i++) {
				ISourceMethod sourceMethod = sourceMethods[i];
				boolean isConstructor = sourceMethod.isConstructor();
				if ((isConstructor && needConstructor) || (!isConstructor && needMethod)) {
					AbstractMethodDeclaration method =convert(sourceMethod, compilationResult);
					if (isInterface || method.isAbstract()) { // fix-up flag 
						method.modifiers |= AccSemicolonBody;
					}
					type.methods[extraConstructor + index++] = method;
				}
			}
		}
		
		return type;
	}

	/*
	 * Build an import reference from an import name, e.g. java.lang.*
	 */
	private ImportReference createImportReference(
		char[] importName,
		int start,
		int end, 
		boolean onDemand,
		int modifiers) {
	
		char[][] qImportName = CharOperation.splitOn('.', importName);
		long[] positions = new long[qImportName.length];
		long position = (long) start << 32 + end;
		for (int i = 0; i < qImportName.length; i++) {
			positions[i] = position; // dummy positions
		}
		return new ImportReference(
			qImportName,
			positions,
			onDemand,
			modifiers);
	}

	/*
	 * Build a type reference from a readable name, e.g. java.lang.Object[][]
	 */
	private TypeReference createTypeReference(
		char[] typeSignature,
		int start,
		int end) {

		/* count identifiers and dimensions */
		int max = typeSignature.length;
		int dimStart = max;
		int dim = 0;
		int identCount = 1;
		for (int i = 0; i < max; i++) {
			switch (typeSignature[i]) {
				case '[' :
					if (dim == 0)
						dimStart = i;
					dim++;
					break;
				case '.' :
					identCount++;
					break;
			}
		}
		/* rebuild identifiers and dimensions */
		if (identCount == 1) { // simple type reference
			if (dim == 0) {
				return new SingleTypeReference(typeSignature, (((long) start )<< 32) + end);
			} else {
				char[] identifier = new char[dimStart];
				System.arraycopy(typeSignature, 0, identifier, 0, dimStart);
				return new ArrayTypeReference(identifier, dim, (((long) start) << 32) + end);
			}
		} else { // qualified type reference
			long[] positions = new long[identCount];
			long pos = (((long) start) << 32) + end;
			for (int i = 0; i < identCount; i++) {
				positions[i] = pos;
			}
			char[][] identifiers =
				CharOperation.splitOn('.', typeSignature, 0, dimStart);
			if (dim == 0) {
				return new QualifiedTypeReference(identifiers, positions);
			} else {
				return new ArrayQualifiedTypeReference(identifiers, dim, positions);
			}
		}
	}
}
