/*******************************************************************************
 * Copyright (c) 2007, 2011 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.structure;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

import org.eclipse.text.edits.TextEdit;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;

import org.eclipse.ltk.core.refactoring.resource.ResourceChange;

import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.NamingConventions;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.compiler.IScanner;
import org.eclipse.jdt.core.compiler.ITerminalSymbols;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;

import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.TokenScanner;
import org.eclipse.jdt.internal.corext.refactoring.ParameterInfo;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.changes.CreateCompilationUnitChange;
import org.eclipse.jdt.internal.corext.refactoring.changes.CreatePackageChange;
import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;

import org.eclipse.jdt.ui.CodeGeneration;

import org.eclipse.jdt.internal.ui.JavaPlugin;

public class ParameterObjectFactory {

	private String fClassName;
	private boolean fCreateGetter;
	private boolean fCreateSetter;
	private String fEnclosingType;
	private String fPackage;
	private List<ParameterInfo> fVariables;

	public ParameterObjectFactory() {
		super();
	}

	public static class CreationListener {
		/**
		 * Notifies that a getter has been created
		 * @param cuRewrite the rewriter
		 * @param getter the new getter
		 * @param pi the parameter info
		 */
		public void getterCreated(CompilationUnitRewrite cuRewrite, MethodDeclaration getter, ParameterInfo pi){}
		/**
		 * Notifies that a setter has been created
		 * @param cuRewrite the rewriter
		 * @param setter the new setter
		 * @param pi the parameter info
		 */
		public void setterCreated(CompilationUnitRewrite cuRewrite, MethodDeclaration setter, ParameterInfo pi){}
		/**
		 * Notifies that a field has been created
		 * @param cuRewrite the rewriter
		 * @param field the new field
		 * @param pi the parameter info
		 */
		public void fieldCreated(CompilationUnitRewrite cuRewrite, FieldDeclaration field, ParameterInfo pi){}
		/**
		 * Notifies that a constructor has been created
		 * @param cuRewrite the rewriter
		 * @param constructor the new constructor
		 */
		public void constructorCreated(CompilationUnitRewrite cuRewrite, MethodDeclaration constructor){}
		/**
		 * Notifies that a type declaration has been created
		 * @param cuRewrite the rewriter
		 * @param declaration the new declaration
		 */
		public void typeCreated(CompilationUnitRewrite cuRewrite, TypeDeclaration declaration) {}

		protected static ASTNode moveNode(CompilationUnitRewrite cuRewrite, ASTNode node) {
			ASTRewrite rewrite= cuRewrite.getASTRewrite();
			if (rewrite.getAST() != node.getAST()) {
				String str= ASTNodes.getNodeSource(node, true, true);
				if (str != null) {
					return rewrite.createStringPlaceholder(str, node.getNodeType());
				}
				return ASTNode.copySubtree(rewrite.getAST(), node);
			}
			return rewrite.createMoveTarget(node);
		}
		/**
		 * Return whether the setter should be created for this field. This method is only called when
		 * the global createSetters is set and the parameterInfo is marked for field creation.
		 * @param pi the parameter info
		 * @return <code>true</code> if a setter should be created
		 */
		public boolean isCreateSetter(ParameterInfo pi) {
			return !Modifier.isFinal(pi.getOldBinding().getModifiers());
		}
		/**
		 * Return whether the getter should be created for this field. This method is only called when
		 * the global createGetters is set and the parameterInfo is marked for field creation.
		 * @param pi the parameter info
		 * @return <code>true</code> if a getter should be created
		 */
		public boolean isCreateGetter(ParameterInfo pi) {
			return true;
		}
		/**
		 * Return whether the field should appear in the constructor
		 * @param pi the parameter info
		 * @return <code>true</code> if the field should appear
		 */
		public boolean isUseInConstructor(ParameterInfo pi) {
			return true;
		}
	}

	/**
	 * Creates a new TypeDeclaration for the parameterInfo objects.
	 *
	 * @param declaringType the fully qualified name of the type
	 * @param cuRewrite the {@link CompilationUnitRewrite} that will be used for creation
	 * @param listener the creation listener or null
	 * @return the new declaration
	 * @throws CoreException if creation failed
	 */
	public TypeDeclaration createClassDeclaration(String declaringType, CompilationUnitRewrite cuRewrite, CreationListener listener) throws CoreException {
		AST ast= cuRewrite.getAST();
		if (listener == null)
			listener= new CreationListener();
		TypeDeclaration typeDeclaration= ast.newTypeDeclaration();
		typeDeclaration.setName(ast.newSimpleName(fClassName));
		List<BodyDeclaration> body= typeDeclaration.bodyDeclarations();
		for (Iterator<ParameterInfo> iter= fVariables.iterator(); iter.hasNext();) {
			ParameterInfo pi= iter.next();
			if (isValidField(pi)) {
				FieldDeclaration declaration= createField(pi, cuRewrite);
				listener.fieldCreated(cuRewrite, declaration, pi);
				body.add(declaration);
			}
		}
		MethodDeclaration constructor= createConstructor(declaringType, cuRewrite, listener);
		listener.constructorCreated(cuRewrite, constructor);
		body.add(constructor);
		for (Iterator<ParameterInfo> iter= fVariables.iterator(); iter.hasNext();) {
			ParameterInfo pi= iter.next();
			if (fCreateGetter && isValidField(pi) && listener.isCreateGetter(pi)) {
				MethodDeclaration getter= createGetter(pi, declaringType, cuRewrite);
				listener.getterCreated(cuRewrite, getter, pi);
				body.add(getter);
			}
			if (fCreateSetter && isValidField(pi) && listener.isCreateSetter(pi)) {
				MethodDeclaration setter= createSetter(pi, declaringType, cuRewrite);
				listener.setterCreated(cuRewrite, setter, pi);
				body.add(setter);
			}
		}
		listener.typeCreated(cuRewrite, typeDeclaration);
		return typeDeclaration;
	}

	private MethodDeclaration createConstructor(String declaringTypeName, CompilationUnitRewrite cuRewrite, CreationListener listener) throws CoreException {
		AST ast= cuRewrite.getAST();
		ICompilationUnit unit= cuRewrite.getCu();
		IJavaProject project= unit.getJavaProject();

		MethodDeclaration methodDeclaration= ast.newMethodDeclaration();
		methodDeclaration.setName(ast.newSimpleName(fClassName));
		methodDeclaration.setConstructor(true);
		methodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD));
		String lineDelimiter= StubUtility.getLineDelimiterUsed(unit);
		if (createComments(project)) {
			String comment= CodeGeneration.getMethodComment(unit, declaringTypeName, methodDeclaration, null, lineDelimiter);
			if (comment != null) {
				Javadoc doc= (Javadoc) cuRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JAVADOC);
				methodDeclaration.setJavadoc(doc);
			}
		}
		List<SingleVariableDeclaration> parameters= methodDeclaration.parameters();
		Block block= ast.newBlock();
		methodDeclaration.setBody(block);
		List<Statement> statements= block.statements();
		List<ParameterInfo> validParameter= new ArrayList<ParameterInfo>();
		for (Iterator<ParameterInfo> iter= fVariables.iterator(); iter.hasNext();) {
			ParameterInfo pi= iter.next();
			if (isValidField(pi) && listener.isUseInConstructor(pi)) {
					validParameter.add(pi);
			}
		}

		ArrayList<String> usedParameter= new ArrayList<String>();
		for (Iterator<ParameterInfo> iter= validParameter.iterator(); iter.hasNext();) {
			ParameterInfo pi= iter.next();
			SingleVariableDeclaration svd= ast.newSingleVariableDeclaration();
			ITypeBinding typeBinding= pi.getNewTypeBinding();
			if (!iter.hasNext() && typeBinding.isArray() && pi.isOldVarargs()) {
				int dimensions= typeBinding.getDimensions();
				if (dimensions == 1) {
					typeBinding= typeBinding.getComponentType();
				} else {
					typeBinding= typeBinding.createArrayType(dimensions - 1);
				}
				svd.setVarargs(true);
			}

			String paramName= getParameterName(pi, project, usedParameter);
			usedParameter.add(paramName);

			Type fieldType= importBinding(typeBinding, cuRewrite);
			svd.setType(fieldType);
			svd.setName(ast.newSimpleName(paramName));
			parameters.add(svd);
			Expression leftHandSide;
			if (paramName.equals(pi.getNewName()) || StubUtility.useThisForFieldAccess(project)) {
				FieldAccess fieldAccess= ast.newFieldAccess();
				fieldAccess.setName(ast.newSimpleName(pi.getNewName()));
				fieldAccess.setExpression(ast.newThisExpression());
				leftHandSide= fieldAccess;
			} else {
				leftHandSide= ast.newSimpleName(pi.getNewName());
			}
			Assignment assignment= ast.newAssignment();
			assignment.setLeftHandSide(leftHandSide);
			assignment.setRightHandSide(ast.newSimpleName(paramName));
			statements.add(ast.newExpressionStatement(assignment));
		}
		return methodDeclaration;
	}

	private String getParameterName(ParameterInfo pi, IJavaProject project, ArrayList<String> usedParameter) {
		String fieldName= pi.getNewName();
		String strippedName= NamingConventions.getBaseName(NamingConventions.VK_INSTANCE_FIELD, fieldName, project);
		String[] suggestions= StubUtility.getVariableNameSuggestions(NamingConventions.VK_PARAMETER, project, strippedName, 0, usedParameter, true);
		return suggestions[0];
	}


	public static Type importBinding(ITypeBinding typeBinding, CompilationUnitRewrite cuRewrite) {
		int declaredModifiers= typeBinding.getDeclaredModifiers();
		AST ast= cuRewrite.getAST();
		if (Modifier.isPrivate(declaredModifiers) || Modifier.isProtected(declaredModifiers)) {
			return ast.newSimpleType(ast.newSimpleName(typeBinding.getName()));
		}
		Type type= cuRewrite.getImportRewrite().addImport(typeBinding, cuRewrite.getAST());
		cuRewrite.getImportRemover().registerAddedImports(type);
		return type;
	}

	private FieldDeclaration createField(ParameterInfo pi, CompilationUnitRewrite cuRewrite) throws CoreException {
		AST ast= cuRewrite.getAST();
		ICompilationUnit unit= cuRewrite.getCu();

		VariableDeclarationFragment fragment= ast.newVariableDeclarationFragment();
		String lineDelim= StubUtility.getLineDelimiterUsed(unit);
		SimpleName fieldName= ast.newSimpleName(pi.getNewName());
		fragment.setName(fieldName);
		FieldDeclaration declaration= ast.newFieldDeclaration(fragment);
		if (createComments(unit.getJavaProject())) {
			String comment= StubUtility.getFieldComment(unit, pi.getNewTypeName(), pi.getNewName(), lineDelim);
			if (comment != null) {
				Javadoc doc= (Javadoc) cuRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JAVADOC);
				declaration.setJavadoc(doc);
			}
		}
		List<Modifier> modifiers= new ArrayList<Modifier>();
		if (fCreateGetter) {
			modifiers.add(ast.newModifier(ModifierKeyword.PRIVATE_KEYWORD));
		} else {
			modifiers.add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD));
		}
		declaration.modifiers().addAll(modifiers);
		declaration.setType(importBinding(pi.getNewTypeBinding(), cuRewrite));
		return declaration;
	}

	public Expression createFieldReadAccess(ParameterInfo pi, String paramName, AST ast, IJavaProject project, boolean useSuper, Expression qualifier) {
		Expression completeQualifier= generateQualifier(paramName, ast, useSuper, qualifier);
		if (fCreateGetter) {
			MethodInvocation mi= ast.newMethodInvocation();
			mi.setName(ast.newSimpleName(getGetterName(pi, ast, project)));
			mi.setExpression(completeQualifier);
			return mi;
		}
		return createFieldAccess(pi, ast, completeQualifier);
	}

	public Expression createFieldWriteAccess(ParameterInfo pi, String paramName, AST ast, IJavaProject project, Expression assignedValue, boolean useSuper, Expression qualifier) {
		Expression completeQualifier= generateQualifier(paramName, ast, useSuper, qualifier);
		if (fCreateSetter) {
			MethodInvocation mi= ast.newMethodInvocation();
			mi.setName(ast.newSimpleName(getSetterName(pi, ast, project)));
			mi.setExpression(completeQualifier);
			mi.arguments().add(assignedValue);
			return mi;
		}
		return createFieldAccess(pi, ast, completeQualifier);
	}

	private Expression generateQualifier(String paramName, AST ast, boolean useSuper, Expression qualifier) {
		SimpleName paramSimpleName= ast.newSimpleName(paramName);
		if (useSuper) {
			SuperFieldAccess sf= ast.newSuperFieldAccess();
			sf.setName(paramSimpleName);
			if (qualifier instanceof Name) {
				sf.setQualifier((Name) qualifier);
			}
			return sf;
		}
		if (qualifier != null) {
			FieldAccess parameterAccess= ast.newFieldAccess();
			parameterAccess.setExpression(qualifier);
			parameterAccess.setName(paramSimpleName);
			return parameterAccess;
		}
		return paramSimpleName;
	}



	private Expression createFieldAccess(ParameterInfo pi, AST ast, Expression qualifier) {
		if (qualifier instanceof Name) {
			Name name= (Name) qualifier; //create FQN for IPOR
			return ast.newName(JavaModelUtil.concatenateName(name.getFullyQualifiedName(), pi.getNewName()));
		}
		FieldAccess fa= ast.newFieldAccess();
		fa.setName(ast.newSimpleName(pi.getNewName()));
		fa.setExpression(qualifier);
		return fa;
	}

	private MethodDeclaration createGetter(ParameterInfo pi, String declaringType, CompilationUnitRewrite cuRewrite) throws CoreException {
		AST ast= cuRewrite.getAST();
		ICompilationUnit cu= cuRewrite.getCu();
		IJavaProject project= cu.getJavaProject();

		MethodDeclaration methodDeclaration= ast.newMethodDeclaration();
		String fieldName= pi.getNewName();
		String getterName= getGetterName(pi, ast, project);
		String lineDelim= StubUtility.getLineDelimiterUsed(cu);
		String bareFieldname= NamingConventions.getBaseName(NamingConventions.VK_INSTANCE_FIELD, fieldName, project);
		if (createComments(project)) {
			String comment= CodeGeneration.getGetterComment(cu, declaringType, getterName, fieldName, pi.getNewTypeName(), bareFieldname, lineDelim);
			if (comment != null)
				methodDeclaration.setJavadoc((Javadoc) cuRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JAVADOC));
		}
		methodDeclaration.setName(ast.newSimpleName(getterName));
		methodDeclaration.setReturnType2(importBinding(pi.getNewTypeBinding(), cuRewrite));
		methodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD));
		Block block= ast.newBlock();
		methodDeclaration.setBody(block);
		boolean useThis= StubUtility.useThisForFieldAccess(project);
		if (useThis) {
			fieldName= "this." + fieldName; //$NON-NLS-1$
		}
		String bodyContent= CodeGeneration.getGetterMethodBodyContent(cu, declaringType, getterName, fieldName, lineDelim);
		ASTNode getterBody= cuRewrite.getASTRewrite().createStringPlaceholder(bodyContent, ASTNode.EXPRESSION_STATEMENT);
		block.statements().add(getterBody);
		return methodDeclaration;
	}

	public ExpressionStatement createInitializer(ParameterInfo pi, String paramName, CompilationUnitRewrite cuRewrite) {
		AST ast= cuRewrite.getAST();

		VariableDeclarationFragment fragment= ast.newVariableDeclarationFragment();
		fragment.setName(ast.newSimpleName(pi.getOldName()));
		fragment.setInitializer(createFieldReadAccess(pi, paramName, ast, cuRewrite.getCu().getJavaProject(), false, null));
		VariableDeclarationExpression declaration= ast.newVariableDeclarationExpression(fragment);
		IVariableBinding variable= pi.getOldBinding();
		declaration.setType(importBinding(pi.getNewTypeBinding(), cuRewrite));
		int modifiers= variable.getModifiers();
		List<Modifier> newModifiers= ast.newModifiers(modifiers);
		declaration.modifiers().addAll(newModifiers);
		return ast.newExpressionStatement(declaration);
	}

	private MethodDeclaration createSetter(ParameterInfo pi, String declaringType, CompilationUnitRewrite cuRewrite) throws CoreException {
		AST ast= cuRewrite.getAST();
		ICompilationUnit cu= cuRewrite.getCu();
		IJavaProject project= cu.getJavaProject();

		MethodDeclaration methodDeclaration= ast.newMethodDeclaration();
		String fieldName= pi.getNewName();
		String setterName= getSetterName(pi, ast, project);
		String lineDelim= StubUtility.getLineDelimiterUsed(cu);
		String bareFieldname= NamingConventions.getBaseName(NamingConventions.VK_INSTANCE_FIELD, fieldName, project);
		String paramName= StubUtility.suggestArgumentName(project, bareFieldname, null);
		if (createComments(project)) {
			String comment= CodeGeneration.getSetterComment(cu, declaringType, setterName, fieldName, pi.getNewTypeName(), paramName, bareFieldname, lineDelim);
			if (comment != null)
				methodDeclaration.setJavadoc((Javadoc) cuRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JAVADOC));
		}
		methodDeclaration.setName(ast.newSimpleName(setterName));
		methodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD));
		SingleVariableDeclaration variable= ast.newSingleVariableDeclaration();
		variable.setType(importBinding(pi.getNewTypeBinding(), cuRewrite));
		variable.setName(ast.newSimpleName(paramName));
		methodDeclaration.parameters().add(variable);
		Block block= ast.newBlock();
		methodDeclaration.setBody(block);
		boolean useThis= StubUtility.useThisForFieldAccess(project);
		if (useThis || fieldName.equals(paramName)) {
			fieldName= "this." + fieldName; //$NON-NLS-1$
		}
		String bodyContent= CodeGeneration.getSetterMethodBodyContent(cu, declaringType, setterName, fieldName, paramName, lineDelim);
		ASTNode setterBody= cuRewrite.getASTRewrite().createStringPlaceholder(bodyContent, ASTNode.EXPRESSION_STATEMENT);
		block.statements().add(setterBody);
		return methodDeclaration;
	}

	public Type createType(boolean asTopLevelClass, CompilationUnitRewrite cuRewrite, int position) {
		String qualifier= asTopLevelClass ? fPackage : fEnclosingType;
		String concatenateName= JavaModelUtil.concatenateName(qualifier, fClassName);
		
		ImportRewrite importRewrite= cuRewrite.getImportRewrite();
		ContextSensitiveImportRewriteContext context= createParameterClassAwareContext(asTopLevelClass, cuRewrite, position);
		String addedImport= importRewrite.addImport(concatenateName, context);
		cuRewrite.getImportRemover().registerAddedImport(addedImport);
		AST ast= cuRewrite.getAST();
		return ast.newSimpleType(ast.newName(addedImport));
	}

	ContextSensitiveImportRewriteContext createParameterClassAwareContext(final boolean asTopLevelClass, final CompilationUnitRewrite cuRewrite, int position) {
		ContextSensitiveImportRewriteContext context= new ContextSensitiveImportRewriteContext(cuRewrite.getRoot(), position, cuRewrite.getImportRewrite()) {
			@Override
			public int findInContext(String qualifier, String name, int kind) {
				String parameterClassName= getClassName();
				if (kind == ImportRewriteContext.KIND_TYPE && parameterClassName.equals(name)) {
					String parameterClassQualifier= asTopLevelClass ? getPackage() : getEnclosingType();
					if (super.findInContext(qualifier, "", kind) == ImportRewriteContext.RES_NAME_FOUND) { //$NON-NLS-1$ // TODO: should be "*", not " "!
						if (parameterClassQualifier.equals(qualifier)) {
							return ImportRewriteContext.RES_NAME_FOUND;
						} else {
							return ImportRewriteContext.RES_NAME_CONFLICT;
						}
					}
				}
				return super.findInContext(qualifier, name, kind);
			}
		};
		return context;
	}

	public String getClassName() {
		return fClassName;
	}

	public String getEnclosingType() {
		return fEnclosingType;
	}

	private String getGetterName(ParameterInfo pi, AST ast, IJavaProject project) {
		ITypeBinding type= pi.getNewTypeBinding();
		boolean isBoolean= ast.resolveWellKnownType("boolean").isEqualTo(type) || ast.resolveWellKnownType("java.lang.Boolean").isEqualTo(type); //$NON-NLS-1$//$NON-NLS-2$
		return NamingConventions.suggestGetterName(project, pi.getNewName(), Flags.AccPublic, isBoolean, null);
	}

	public String getPackage() {
		return fPackage;
	}

	public ParameterInfo getParameterInfo(String identifier) {
		for (Iterator<ParameterInfo> iter= fVariables.iterator(); iter.hasNext();) {
			ParameterInfo pi= iter.next();
			if (pi.getOldName().equals(identifier))
				return pi;
		}
		return null;
	}

	private String getSetterName(ParameterInfo pi, AST ast, IJavaProject project) {
		ITypeBinding type= pi.getNewTypeBinding();
		boolean isBoolean= ast.resolveWellKnownType("boolean").isEqualTo(type) || ast.resolveWellKnownType("java.lang.Boolean").isEqualTo(type); //$NON-NLS-1$//$NON-NLS-2$
		return NamingConventions.suggestSetterName(project, pi.getNewName(), Flags.AccPublic, isBoolean, null);
	}

	public boolean isCreateGetter() {
		return fCreateGetter;
	}

	public boolean isCreateSetter() {
		return fCreateSetter;
	}

	private boolean isValidField(ParameterInfo pi) {
		return pi.isCreateField() && !pi.isAdded();
	}

	public void moveDown(ParameterInfo selected) {
		int idx= fVariables.indexOf(selected);
		Assert.isTrue(idx >= 0 && idx < fVariables.size() - 1);
		int nextIdx= idx + 1;
		ParameterInfo next= fVariables.get(nextIdx);
		if (next.isAdded()) {
			nextIdx++;
			Assert.isTrue(nextIdx <= fVariables.size() - 1);
			next= fVariables.get(nextIdx);
		}
		fVariables.set(idx, next);
		fVariables.set(nextIdx, selected);
	}

	public void moveUp(ParameterInfo selected) {
		int idx= fVariables.indexOf(selected);
		Assert.isTrue(idx > 0);
		int prevIdx= idx - 1;
		ParameterInfo prev= fVariables.get(prevIdx);
		if (prev.isAdded()) {
			prevIdx--;
			Assert.isTrue(prevIdx >= 0);
			prev= fVariables.get(prevIdx);
		}
		fVariables.set(idx, prev);
		fVariables.set(prevIdx, selected);
	}

	public void setClassName(String className) {
		fClassName= className;
	}

	public void setCreateGetter(boolean createGetter) {
		fCreateGetter= createGetter;
	}

	public void setCreateSetter(boolean createSetter) {
		fCreateSetter= createSetter;
	}

	public void setEnclosingType(String enclosingType) {
		fEnclosingType= enclosingType;
	}

	public void setPackage(String typeQualifier) {
		fPackage= typeQualifier;
	}

	public void setVariables(List<ParameterInfo> parameters) {
		fVariables= parameters;
	}

	/**
	 * Updates the position of the newly inserted parameterObject so that it is
	 * directly after the first checked parameter
	 *
	 * @param parameterObjectReference the inserted parameterObject
	 */
	public void updateParameterPosition(ParameterInfo parameterObjectReference) {
		fVariables.remove(parameterObjectReference);
		for (ListIterator<ParameterInfo> iterator= fVariables.listIterator(); iterator.hasNext();) {
			ParameterInfo pi= iterator.next();
			if (isValidField(pi)) {
				iterator.add(parameterObjectReference);
				return;
			}
		}
	}

	private boolean createComments(IJavaProject project) {
		return StubUtility.doAddComments(project);
	}


	public List<ResourceChange> createTopLevelParameterObject(IPackageFragmentRoot packageFragmentRoot, CreationListener listener) throws CoreException {
		List<ResourceChange> changes= new ArrayList<ResourceChange>();
		IPackageFragment packageFragment= packageFragmentRoot.getPackageFragment(getPackage());
		if (!packageFragment.exists()) {
			changes.add(new CreatePackageChange(packageFragment));
		}
		ICompilationUnit unit= packageFragment.getCompilationUnit(getClassName() + JavaModelUtil.DEFAULT_CU_SUFFIX);
		Assert.isTrue(!unit.exists());
		IJavaProject javaProject= unit.getJavaProject();
		ICompilationUnit workingCopy= unit.getWorkingCopy(null);

		try {
			// create stub with comments and dummy type
			String lineDelimiter= StubUtility.getLineDelimiterUsed(javaProject);
			String fileComment= getFileComment(workingCopy, lineDelimiter);
			String typeComment= getTypeComment(workingCopy, lineDelimiter);
			String content= CodeGeneration.getCompilationUnitContent(workingCopy, fileComment, typeComment, "class " + getClassName() + "{}", lineDelimiter); //$NON-NLS-1$ //$NON-NLS-2$
			workingCopy.getBuffer().setContents(content);

			CompilationUnitRewrite cuRewrite= new CompilationUnitRewrite(workingCopy);
			ASTRewrite rewriter= cuRewrite.getASTRewrite();
			CompilationUnit root= cuRewrite.getRoot();
			AST ast= cuRewrite.getAST();
			ImportRewrite importRewrite= cuRewrite.getImportRewrite();

			// retrieve&replace dummy type with real class
			ListRewrite types= rewriter.getListRewrite(root, CompilationUnit.TYPES_PROPERTY);
			ASTNode dummyType= (ASTNode) types.getOriginalList().get(0);
			String newTypeName= JavaModelUtil.concatenateName(getPackage(), getClassName());
			TypeDeclaration classDeclaration= createClassDeclaration(newTypeName, cuRewrite, listener);
			classDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD));
			Javadoc javadoc= (Javadoc) dummyType.getStructuralProperty(TypeDeclaration.JAVADOC_PROPERTY);
			rewriter.set(classDeclaration, TypeDeclaration.JAVADOC_PROPERTY, javadoc, null);
			types.replace(dummyType, classDeclaration, null);

			// Apply rewrites and discard workingcopy
			// Using CompilationUnitRewrite.createChange() leads to strange
			// results
			String charset= ResourceUtil.getFile(unit).getCharset(false);
			Document document= new Document(content);
			try {
				rewriter.rewriteAST().apply(document);
				TextEdit rewriteImports= importRewrite.rewriteImports(null);
				rewriteImports.apply(document);
			} catch (BadLocationException e) {
				throw new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), RefactoringCoreMessages.IntroduceParameterObjectRefactoring_parameter_object_creation_error, e));
			}
			String docContent= document.get();
			CreateCompilationUnitChange compilationUnitChange= new CreateCompilationUnitChange(unit, docContent, charset);
			changes.add(compilationUnitChange);
		} finally {
			workingCopy.discardWorkingCopy();
		}
		return changes;
	}

	public List<ResourceChange> createTopLevelParameterObject(IPackageFragmentRoot packageFragmentRoot) throws CoreException {
		return createTopLevelParameterObject(packageFragmentRoot, null);
	}

	protected String getFileComment(ICompilationUnit parentCU, String lineDelimiter) throws CoreException {
		if (StubUtility.doAddComments(parentCU.getJavaProject())) {
			return CodeGeneration.getFileComment(parentCU, lineDelimiter);
		}
		return null;

	}

	protected String getTypeComment(ICompilationUnit parentCU, String lineDelimiter) throws CoreException {
		if (StubUtility.doAddComments(parentCU.getJavaProject())) {
			StringBuffer typeName= new StringBuffer();
			typeName.append(getClassName());
			String[] typeParamNames= new String[0];
			String comment= CodeGeneration.getTypeComment(parentCU, typeName.toString(), typeParamNames, lineDelimiter);
			if (comment != null && isValidComment(comment)) {
				return comment;
			}
		}
		return null;
	}

	private boolean isValidComment(String template) {
		IScanner scanner= ToolFactory.createScanner(true, false, false, false);
		scanner.setSource(template.toCharArray());
		try {
			int next= scanner.getNextToken();
			while (TokenScanner.isComment(next)) {
				next= scanner.getNextToken();
			}
			return next == ITerminalSymbols.TokenNameEOF;
		} catch (InvalidInputException e) {
		}
		return false;
	}

}
