package gr.uom.java.jdeodorant.refactoring.manipulators;

import gr.uom.java.ast.ASTReader;
import gr.uom.java.ast.decomposition.cfg.PlainVariable;
import gr.uom.java.ast.util.ExpressionExtractor;
import gr.uom.java.ast.util.MethodDeclarationUtility;
import gr.uom.java.ast.util.StatementExtractor;
import gr.uom.java.ast.util.ThrownExceptionVisitor;
import gr.uom.java.ast.util.TypeVisitor;
import gr.uom.java.ast.util.math.AdjacencyList;
import gr.uom.java.ast.util.math.Edge;
import gr.uom.java.ast.util.math.Node;
import gr.uom.java.ast.util.math.TarjanAlgorithm;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.ArrayAccess;
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.CastExpression;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnumDeclaration;
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.IBinding;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.IPackageBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
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.Name;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression.Operator;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.QualifiedType;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.ThrowStatement;
import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.internal.corext.refactoring.changes.CreateCompilationUnitChange;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.ChangeDescriptor;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;

@SuppressWarnings("restriction")
public class ExtractClassRefactoring extends Refactoring {
	private static final String GETTER_PREFIX = "get";
	private static final String SETTER_PREFIX = "set";
	private static final String ACCESSOR_SUFFIX = "2";
	private IFile sourceFile;
	private CompilationUnit sourceCompilationUnit;
	private TypeDeclaration sourceTypeDeclaration;
	private Map<ICompilationUnit, CompilationUnitChange> compilationUnitChanges;
	private Map<ICompilationUnit, CreateCompilationUnitChange> createCompilationUnitChanges;
	private Set<IJavaElement> javaElementsToOpenInEditor;
	private Set<ITypeBinding> requiredImportDeclarationsInExtractedClass;
	private Map<MethodDeclaration, Set<PlainVariable>> additionalArgumentsAddedToExtractedMethods;
	private Map<MethodDeclaration, Set<SingleVariableDeclaration>> additionalParametersAddedToExtractedMethods;
	private Set<String> sourceMethodBindingsChangedWithPublicModifier;
	private Set<String> sourceFieldBindingsWithCreatedSetterMethod;
	private Set<String> sourceFieldBindingsWithCreatedGetterMethod;
	private Set<FieldDeclaration> fieldDeclarationsChangedWithPublicModifier;
	private Set<BodyDeclaration> memberTypeDeclarationsChangedWithPublicModifier;
	private Map<MethodDeclaration, Set<MethodInvocation>> oldMethodInvocationsWithinExtractedMethods;
	private Map<MethodDeclaration, Set<MethodInvocation>> newMethodInvocationsWithinExtractedMethods;
	private Map<MethodDeclaration, MethodDeclaration> oldToNewExtractedMethodDeclarationMap;
	private Set<VariableDeclaration> extractedFieldFragments;
	private Set<MethodDeclaration> extractedMethods;
	private Set<MethodDeclaration> delegateMethods;
	private String extractedTypeName;
	private boolean leaveDelegateForPublicMethods;
	private Map<Statement, ASTRewrite> statementRewriteMap;
	//this map holds for each constructor the assignment statements that initialize final extracted fields
	private Map<MethodDeclaration, Map<VariableDeclaration, Assignment>> constructorFinalFieldAssignmentMap;
	//this map hold the parameters that should be passed in each constructor of the extracted class
	private Map<MethodDeclaration, Set<VariableDeclaration>> extractedClassConstructorParameterMap;
	private Set<VariableDeclaration> extractedFieldsWithThisExpressionInTheirInitializer;
	private Set<IMethodBinding> staticallyImportedMethods;
	
	public ExtractClassRefactoring(IFile sourceFile, CompilationUnit sourceCompilationUnit, TypeDeclaration sourceTypeDeclaration,
			Set<VariableDeclaration> extractedFieldFragments, Set<MethodDeclaration> extractedMethods, Set<MethodDeclaration> delegateMethods, String extractedTypeName) {
		this.sourceFile = sourceFile;
		this.sourceCompilationUnit = sourceCompilationUnit;
		this.sourceTypeDeclaration = sourceTypeDeclaration;
		this.compilationUnitChanges = new LinkedHashMap<ICompilationUnit, CompilationUnitChange>();
		ICompilationUnit sourceICompilationUnit = (ICompilationUnit)sourceCompilationUnit.getJavaElement();
		MultiTextEdit sourceMultiTextEdit = new MultiTextEdit();
		CompilationUnitChange sourceCompilationUnitChange = new CompilationUnitChange("", sourceICompilationUnit);
		sourceCompilationUnitChange.setEdit(sourceMultiTextEdit);
		this.compilationUnitChanges.put(sourceICompilationUnit, sourceCompilationUnitChange);
		this.createCompilationUnitChanges = new LinkedHashMap<ICompilationUnit, CreateCompilationUnitChange>();
		this.javaElementsToOpenInEditor = new LinkedHashSet<IJavaElement>();
		this.requiredImportDeclarationsInExtractedClass = new LinkedHashSet<ITypeBinding>();
		this.additionalArgumentsAddedToExtractedMethods = new LinkedHashMap<MethodDeclaration, Set<PlainVariable>>();
		this.additionalParametersAddedToExtractedMethods = new LinkedHashMap<MethodDeclaration, Set<SingleVariableDeclaration>>();
		this.sourceMethodBindingsChangedWithPublicModifier = new LinkedHashSet<String>();
		this.sourceFieldBindingsWithCreatedSetterMethod = new LinkedHashSet<String>();
		this.sourceFieldBindingsWithCreatedGetterMethod = new LinkedHashSet<String>();
		this.fieldDeclarationsChangedWithPublicModifier = new LinkedHashSet<FieldDeclaration>();
		this.memberTypeDeclarationsChangedWithPublicModifier = new LinkedHashSet<BodyDeclaration>();
		this.oldMethodInvocationsWithinExtractedMethods = new LinkedHashMap<MethodDeclaration, Set<MethodInvocation>>();
		this.newMethodInvocationsWithinExtractedMethods = new LinkedHashMap<MethodDeclaration, Set<MethodInvocation>>();
		this.oldToNewExtractedMethodDeclarationMap = new LinkedHashMap<MethodDeclaration, MethodDeclaration>();
		this.extractedFieldFragments = extractedFieldFragments;
		this.extractedMethods = extractedMethods;
		this.delegateMethods = delegateMethods;
		this.extractedTypeName = extractedTypeName;
		this.leaveDelegateForPublicMethods = false;
		this.statementRewriteMap = new LinkedHashMap<Statement, ASTRewrite>();
		this.constructorFinalFieldAssignmentMap = new LinkedHashMap<MethodDeclaration, Map<VariableDeclaration, Assignment>>();
		this.extractedClassConstructorParameterMap = new LinkedHashMap<MethodDeclaration, Set<VariableDeclaration>>();
		this.extractedFieldsWithThisExpressionInTheirInitializer = new LinkedHashSet<VariableDeclaration>();
		this.staticallyImportedMethods = new LinkedHashSet<IMethodBinding>();
		for(MethodDeclaration extractedMethod : extractedMethods) {
			additionalArgumentsAddedToExtractedMethods.put(extractedMethod, new LinkedHashSet<PlainVariable>());
			additionalParametersAddedToExtractedMethods.put(extractedMethod, new LinkedHashSet<SingleVariableDeclaration>());
		}
	}

	public String getExtractedTypeName() {
		return extractedTypeName;
	}

	public void setExtractedTypeName(String targetTypeName) {
		this.extractedTypeName = targetTypeName;
	}

	public void setLeaveDelegateForPublicMethods(boolean leaveDelegateForPublicMethods) {
		this.leaveDelegateForPublicMethods = leaveDelegateForPublicMethods;
	}

	public CompilationUnit getSourceCompilationUnit() {
		return sourceCompilationUnit;
	}

	public Set<IJavaElement> getJavaElementsToOpenInEditor() {
		return javaElementsToOpenInEditor;
	}

	private String appendAccessorMethodSuffix(String accessorMethodName, MethodDeclaration[] methodDeclarations) {
		return appendAccessorMethodSuffix(accessorMethodName, new LinkedHashSet<MethodDeclaration>(Arrays.asList(methodDeclarations)));
	}

	private String appendAccessorMethodSuffix(String accessorMethodName, Set<MethodDeclaration> methodDeclarations) {
		boolean sourceTypeContainsMethodWithAccessorNameThatIsNotPureAccessor = false;
		for(MethodDeclaration methodDeclaration : methodDeclarations) {
			if(methodDeclaration.getName().getIdentifier().equals(accessorMethodName)) {
				if(accessorMethodName.startsWith(GETTER_PREFIX)) {
					SimpleName simpleName = MethodDeclarationUtility.isGetter(methodDeclaration);
					if(simpleName == null) {
						sourceTypeContainsMethodWithAccessorNameThatIsNotPureAccessor = true;
						break;
					}
				}
				else if(accessorMethodName.startsWith(SETTER_PREFIX)) {
					SimpleName simpleName = MethodDeclarationUtility.isSetter(methodDeclaration);
					if(simpleName == null) {
						sourceTypeContainsMethodWithAccessorNameThatIsNotPureAccessor = true;
						break;
					}
				}
			}
		}
		if(sourceTypeContainsMethodWithAccessorNameThatIsNotPureAccessor) {
			accessorMethodName += ACCESSOR_SUFFIX;
		}
		return accessorMethodName;
	}

	public void apply() {
		for(MethodDeclaration method : extractedMethods) {
			int modifiers = method.getModifiers();
			if((modifiers & Modifier.PRIVATE) == 0)
				delegateMethods.add(method);
		}
		removeFieldFragmentsInSourceClass(extractedFieldFragments);
		Set<VariableDeclaration> modifiedFieldsInNonExtractedMethods = new LinkedHashSet<VariableDeclaration>();
		Set<VariableDeclaration> accessedFieldsInNonExtractedMethods = new LinkedHashSet<VariableDeclaration>();
		modifyExtractedFieldAssignmentsInSourceClass(extractedFieldFragments, modifiedFieldsInNonExtractedMethods, accessedFieldsInNonExtractedMethods);
		modifyExtractedFieldAccessesInSourceClass(extractedFieldFragments, accessedFieldsInNonExtractedMethods);
		createExtractedTypeFieldReferenceInSourceClass();
		
		Set<ITypeBinding> typeBindings = new LinkedHashSet<ITypeBinding>();
		TypeVisitor typeVisitor = new TypeVisitor();
		for(VariableDeclaration fieldFragment : extractedFieldFragments) {
			fieldFragment.getParent().accept(typeVisitor);
			for(ITypeBinding typeBinding : typeVisitor.getTypeBindings()) {
				typeBindings.add(typeBinding);
			}
		}
		for(MethodDeclaration method : extractedMethods) {
			method.accept(typeVisitor);
			for(ITypeBinding typeBinding : typeVisitor.getTypeBindings()) {
				typeBindings.add(typeBinding);
			}
		}
		RefactoringUtility.getSimpleTypeBindings(typeBindings, requiredImportDeclarationsInExtractedClass);
		
		createExtractedClass(modifiedFieldsInNonExtractedMethods, accessedFieldsInNonExtractedMethods);
		modifyExtractedMethodInvocationsInSourceClass();
		handleInitializationOfExtractedFieldsWithThisExpressionInTheirInitializer();
		for(Statement statement : statementRewriteMap.keySet()) {
			ASTRewrite sourceRewriter = statementRewriteMap.get(statement);
			try {
				TextEdit sourceEdit = sourceRewriter.rewriteAST();
				ICompilationUnit sourceICompilationUnit = (ICompilationUnit)sourceCompilationUnit.getJavaElement();
				CompilationUnitChange change = compilationUnitChanges.get(sourceICompilationUnit);
				change.getEdit().addChild(sourceEdit);
				change.addTextEditGroup(new TextEditGroup("Change access of extracted member", new TextEdit[] {sourceEdit}));
			}
			catch(JavaModelException javaModelException) {
				javaModelException.printStackTrace();
			}
		}
		Set<MethodDeclaration> methodsToBeRemoved = new LinkedHashSet<MethodDeclaration>();
		for(MethodDeclaration method : extractedMethods) {
			if(delegateMethods.contains(method))
				addDelegationInExtractedMethod(method);
			else
				methodsToBeRemoved.add(method);
		}
		if(methodsToBeRemoved.size() > 0)
			removeSourceMethods(methodsToBeRemoved);
	}

	private void handleInitializationOfExtractedFieldsWithThisExpressionInTheirInitializer() {
		String modifiedExtractedTypeName = extractedTypeName.substring(0,1).toLowerCase() + extractedTypeName.substring(1,extractedTypeName.length());
		for(VariableDeclaration fieldFragment : extractedFieldsWithThisExpressionInTheirInitializer) {
			String originalFieldName = fieldFragment.getName().getIdentifier();
			String modifiedFieldName = originalFieldName.substring(0,1).toUpperCase() + originalFieldName.substring(1,originalFieldName.length());
			for(MethodDeclaration methodDeclaration : sourceTypeDeclaration.getMethods()) {
				if(methodDeclaration.isConstructor()) {
					ASTRewrite sourceRewriter = ASTRewrite.create(sourceCompilationUnit.getAST());
					ListRewrite constructorBodyRewrite = sourceRewriter.getListRewrite(methodDeclaration.getBody(), Block.STATEMENTS_PROPERTY);
					AST contextAST = sourceTypeDeclaration.getAST();
					MethodInvocation setterMethodInvocation = contextAST.newMethodInvocation();
					sourceRewriter.set(setterMethodInvocation, MethodInvocation.NAME_PROPERTY, contextAST.newSimpleName(SETTER_PREFIX + modifiedFieldName), null);
					ListRewrite setterMethodInvocationArgumentsRewrite = sourceRewriter.getListRewrite(setterMethodInvocation, MethodInvocation.ARGUMENTS_PROPERTY);
					setterMethodInvocationArgumentsRewrite.insertLast(fieldFragment.getInitializer(), null);
					if((fieldFragment.resolveBinding().getModifiers() & Modifier.STATIC) != 0) {
						sourceRewriter.set(setterMethodInvocation, MethodInvocation.EXPRESSION_PROPERTY, contextAST.newSimpleName(extractedTypeName), null);
					}
					else {
						sourceRewriter.set(setterMethodInvocation, MethodInvocation.EXPRESSION_PROPERTY, contextAST.newSimpleName(modifiedExtractedTypeName), null);
					}
					ExpressionStatement expressionStatement = contextAST.newExpressionStatement(setterMethodInvocation);
					constructorBodyRewrite.insertLast(expressionStatement, null);
					try {
						TextEdit sourceEdit = sourceRewriter.rewriteAST();
						ICompilationUnit sourceICompilationUnit = (ICompilationUnit)sourceCompilationUnit.getJavaElement();
						CompilationUnitChange change = compilationUnitChanges.get(sourceICompilationUnit);
						change.getEdit().addChild(sourceEdit);
						change.addTextEditGroup(new TextEditGroup("Initialize extracted field " + fieldFragment.getName().getIdentifier(), new TextEdit[] {sourceEdit}));
					}
					catch(JavaModelException javaModelException) {
						javaModelException.printStackTrace();
					}
				}
			}
		}
	}

	private void addDelegationInExtractedMethod(MethodDeclaration sourceMethod) {
		List<SingleVariableDeclaration> sourceMethodParameters = sourceMethod.parameters();
		
		ASTRewrite sourceRewriter = ASTRewrite.create(sourceCompilationUnit.getAST());
		ListRewrite methodBodyRewrite = sourceRewriter.getListRewrite(sourceMethod.getBody(), Block.STATEMENTS_PROPERTY);
		List<Statement> sourceMethodStatements = sourceMethod.getBody().statements();
		for(Statement statement : sourceMethodStatements) {
			methodBodyRewrite.remove(statement, null);
		}
		
		Type sourceMethodReturnType = sourceMethod.getReturnType2();
		ITypeBinding sourceMethodReturnTypeBinding = sourceMethodReturnType.resolveBinding();
		AST ast = sourceMethod.getBody().getAST();
		MethodInvocation delegation = ast.newMethodInvocation();
		sourceRewriter.set(delegation, MethodInvocation.NAME_PROPERTY, sourceMethod.getName(), null);
		if((sourceMethod.getModifiers() & Modifier.STATIC) != 0) {
			sourceRewriter.set(delegation, MethodInvocation.EXPRESSION_PROPERTY, ast.newSimpleName(extractedTypeName), null);
		}
		else {
			String modifiedExtractedTypeName = extractedTypeName.substring(0,1).toLowerCase() + extractedTypeName.substring(1,extractedTypeName.length());
			SimpleName expressionName = ast.newSimpleName(modifiedExtractedTypeName);
			sourceRewriter.set(delegation, MethodInvocation.EXPRESSION_PROPERTY, expressionName, null);
		}
		
		ListRewrite argumentRewrite = sourceRewriter.getListRewrite(delegation, MethodInvocation.ARGUMENTS_PROPERTY);
		for(SingleVariableDeclaration parameter : sourceMethodParameters) {
			SimpleName argumentName = ast.newSimpleName(parameter.getName().getIdentifier());
			argumentRewrite.insertLast(argumentName, null);
		}
		Set<PlainVariable> additionalArgumentsAddedToMovedMethod = additionalArgumentsAddedToExtractedMethods.get(sourceMethod);
		for(PlainVariable argument : additionalArgumentsAddedToMovedMethod) {
			if(isThisVariable(argument)) {
				argumentRewrite.insertLast(ast.newThisExpression(), null);
			}
			else {
				if(argument.isField()) {
					FieldAccess fieldAccess = ast.newFieldAccess();
					sourceRewriter.set(fieldAccess, FieldAccess.NAME_PROPERTY, ast.newSimpleName(argument.getVariableName()), null);
					sourceRewriter.set(fieldAccess, FieldAccess.EXPRESSION_PROPERTY, ast.newThisExpression(), null);
					argumentRewrite.insertLast(fieldAccess, null);
				}
				else {
					argumentRewrite.insertLast(ast.newSimpleName(argument.getVariableName()), null);
				}
			}
		}
		if(sourceMethodReturnTypeBinding.getName().equals("void")) {
			ExpressionStatement expressionStatement = ast.newExpressionStatement(delegation);
			methodBodyRewrite.insertLast(expressionStatement, null);
		}
		else {
			ReturnStatement returnStatement = ast.newReturnStatement();
			sourceRewriter.set(returnStatement, ReturnStatement.EXPRESSION_PROPERTY, delegation, null);
			methodBodyRewrite.insertLast(returnStatement, null);
		}
		
		try {
			TextEdit sourceEdit = sourceRewriter.rewriteAST();
			ICompilationUnit sourceICompilationUnit = (ICompilationUnit)sourceCompilationUnit.getJavaElement();
			CompilationUnitChange change = compilationUnitChanges.get(sourceICompilationUnit);
			change.getEdit().addChild(sourceEdit);
			change.addTextEditGroup(new TextEditGroup("Leave delegate to extracted method " + sourceMethod.getName().getIdentifier(), new TextEdit[] {sourceEdit}));
		}
		catch(JavaModelException javaModelException) {
			javaModelException.printStackTrace();
		}
	}

	private void removeSourceMethods(Set<MethodDeclaration> methods) {
		//Eclipse bug workaround: Overlapping TextEdits Exception when the x last methods of a type declaration are being removed
		List<Integer> removedMethodPositions = new ArrayList<Integer>();
		MethodDeclaration[] sourceMethods = sourceTypeDeclaration.getMethods();
		int numberOfMethods = sourceMethods.length;
		int i = 0;
		for(MethodDeclaration sourceMethod : sourceMethods) {
			if(methods.contains(sourceMethod)) {
				removedMethodPositions.add(i);
			}
			i++;
		}
		Set<MethodDeclaration> methodsToBeRemovedTogether = new LinkedHashSet<MethodDeclaration>();
		int lastMethodPosition = numberOfMethods - 1;
		if(removedMethodPositions.get(removedMethodPositions.size() - 1) == lastMethodPosition) {
			int validIndex = lastMethodPosition;
			for(int j=removedMethodPositions.size()-1; j>=0; j--) {
				if(removedMethodPositions.get(j) == validIndex)
					methodsToBeRemovedTogether.add(sourceMethods[removedMethodPositions.get(j)]);
				else
					break;
				validIndex--;
			}
		}
		for(MethodDeclaration method : methods) {
			if(!methodsToBeRemovedTogether.contains(method)) {
				ASTRewrite sourceRewriter = ASTRewrite.create(sourceCompilationUnit.getAST());
				ListRewrite classBodyRewrite = sourceRewriter.getListRewrite(sourceTypeDeclaration, TypeDeclaration.BODY_DECLARATIONS_PROPERTY);
				classBodyRewrite.remove(method, null);
				try {
					TextEdit sourceEdit = sourceRewriter.rewriteAST();
					ICompilationUnit sourceICompilationUnit = (ICompilationUnit)sourceCompilationUnit.getJavaElement();
					CompilationUnitChange change = compilationUnitChanges.get(sourceICompilationUnit);
					change.getEdit().addChild(sourceEdit);
					change.addTextEditGroup(new TextEditGroup("Remove extracted method " + method.getName().getIdentifier(), new TextEdit[] {sourceEdit}));
				}
				catch(JavaModelException javaModelException) {
					javaModelException.printStackTrace();
				}
			}
		}
		if(!methodsToBeRemovedTogether.isEmpty()) {
			ASTRewrite sourceRewriter = ASTRewrite.create(sourceCompilationUnit.getAST());
			ListRewrite classBodyRewrite = sourceRewriter.getListRewrite(sourceTypeDeclaration, TypeDeclaration.BODY_DECLARATIONS_PROPERTY);
			for(MethodDeclaration method : methodsToBeRemovedTogether) {
				classBodyRewrite.remove(method, null);
			}
			try {
				TextEdit sourceEdit = sourceRewriter.rewriteAST();
				ICompilationUnit sourceICompilationUnit = (ICompilationUnit)sourceCompilationUnit.getJavaElement();
				CompilationUnitChange change = compilationUnitChanges.get(sourceICompilationUnit);
				change.getEdit().addChild(sourceEdit);
				change.addTextEditGroup(new TextEditGroup("Remove extracted method", new TextEdit[] {sourceEdit}));
			}
			catch(JavaModelException javaModelException) {
				javaModelException.printStackTrace();
			}
		}
	}

	private void modifyExtractedMethodInvocationsInSourceClass() {
		ExpressionExtractor expressionExtractor = new ExpressionExtractor();
		Set<MethodDeclaration> contextMethods = getAllMethodDeclarationsInSourceClass();
		for(MethodDeclaration methodDeclaration : contextMethods) {
			if(!extractedMethods.contains(methodDeclaration)) {
				Block methodBody = methodDeclaration.getBody();
				if(methodBody != null) {
					List<Statement> statements = methodBody.statements();
					for(Statement statement : statements) {
						ASTRewrite sourceRewriter = null;
						if(statementRewriteMap.containsKey(statement)) {
							sourceRewriter = statementRewriteMap.get(statement);
						}
						else {
							sourceRewriter = ASTRewrite.create(sourceTypeDeclaration.getAST());
						}
						AST ast = sourceTypeDeclaration.getAST();
						boolean rewriteAST = false;
						List<Expression> methodInvocations = expressionExtractor.getMethodInvocations(statement);
						for(Expression expression : methodInvocations) {
							if(expression instanceof MethodInvocation) {
								MethodInvocation methodInvocation = (MethodInvocation)expression;
								MethodDeclaration extractedMethod = getExtractedMethod(methodInvocation.resolveMethodBinding());
								if(extractedMethod != null) {
									if(methodInvocation.getExpression() == null || methodInvocation.getExpression() instanceof ThisExpression) {
										ListRewrite argumentRewrite = sourceRewriter.getListRewrite(methodInvocation, MethodInvocation.ARGUMENTS_PROPERTY);
										Set<PlainVariable> additionalArgumentsAddedToMovedMethod = additionalArgumentsAddedToExtractedMethods.get(extractedMethod);
										for(PlainVariable argument : additionalArgumentsAddedToMovedMethod) {
											if(isThisVariable(argument)) {
												if(isParentAnonymousClassDeclaration(methodInvocation)) {
													ThisExpression thisExpression = ast.newThisExpression();
													sourceRewriter.set(thisExpression, ThisExpression.QUALIFIER_PROPERTY, sourceTypeDeclaration.getName(), null);
													argumentRewrite.insertLast(thisExpression, null);
												}
												else {
													argumentRewrite.insertLast(ast.newThisExpression(), null);
												}
											}
											else {
												if(argument.isField()) {
													FieldAccess fieldAccess = ast.newFieldAccess();
													sourceRewriter.set(fieldAccess, FieldAccess.NAME_PROPERTY, ast.newSimpleName(argument.getVariableName()), null);
													sourceRewriter.set(fieldAccess, FieldAccess.EXPRESSION_PROPERTY, ast.newThisExpression(), null);
													argumentRewrite.insertLast(fieldAccess, null);
												}
												else {
													argumentRewrite.insertLast(ast.newSimpleName(argument.getVariableName()), null);
												}
											}
										}
										if((extractedMethod.getModifiers() & Modifier.STATIC) != 0) {
											sourceRewriter.set(methodInvocation, MethodInvocation.EXPRESSION_PROPERTY, ast.newSimpleName(extractedTypeName), null);
										}
										else {
											String modifiedExtractedTypeName = extractedTypeName.substring(0,1).toLowerCase() + extractedTypeName.substring(1,extractedTypeName.length());
											sourceRewriter.set(methodInvocation, MethodInvocation.EXPRESSION_PROPERTY, ast.newSimpleName(modifiedExtractedTypeName), null);
										}
										rewriteAST = true;
									}
									else {
										delegateMethods.add(extractedMethod);
									}
								}
							}
						}
						if(rewriteAST) {
							if(!statementRewriteMap.containsKey(statement))
								statementRewriteMap.put(statement, sourceRewriter);
							/*try {
								TextEdit sourceEdit = sourceRewriter.rewriteAST();
								ICompilationUnit sourceICompilationUnit = (ICompilationUnit)sourceCompilationUnit.getJavaElement();
								CompilationUnitChange change = compilationUnitChanges.get(sourceICompilationUnit);
								change.getEdit().addChild(sourceEdit);
								change.addTextEditGroup(new TextEditGroup("Change invocation of extracted method", new TextEdit[] {sourceEdit}));
							}
							catch(JavaModelException javaModelException) {
								javaModelException.printStackTrace();
							}*/
						}
					}
				}
			}
		}
	}

	private boolean existsNonTransientExtractedFieldFragment() {
		for(VariableDeclaration fieldFragment : extractedFieldFragments) {
			FieldDeclaration originalFieldDeclaration = (FieldDeclaration)fieldFragment.getParent();
			List<IExtendedModifier> originalModifiers = originalFieldDeclaration.modifiers();
			boolean transientFound = false;
    		for(IExtendedModifier extendedModifier : originalModifiers) {
    			if(extendedModifier.isModifier()) {
    				Modifier modifier = (Modifier)extendedModifier;
    				if(modifier.isTransient()) {
    					transientFound = true;
    					break;
    				}
    			}
    		}
    		if(!transientFound) {
    			return true;
    		}
		}
		return false;
	}

	private ITypeBinding implementsSerializableInterface(ITypeBinding typeBinding) {
		ITypeBinding[] implementedInterfaces = typeBinding.getInterfaces();
		for(ITypeBinding implementedInterface : implementedInterfaces) {
			if(implementedInterface.getQualifiedName().equals("java.io.Serializable")) {
				return implementedInterface;
			}
		}
		ITypeBinding superclassTypeBinding = typeBinding.getSuperclass();
		if(superclassTypeBinding != null) {
			return implementsSerializableInterface(superclassTypeBinding);
		}
		return null;
	}

	private ITypeBinding implementsCloneableInterface(ITypeBinding typeBinding) {
		ITypeBinding[] implementedInterfaces = typeBinding.getInterfaces();
		for(ITypeBinding implementedInterface : implementedInterfaces) {
			if(implementedInterface.getQualifiedName().equals("java.lang.Cloneable")) {
				return implementedInterface;
			}
			ITypeBinding[] superInterfaces = implementedInterface.getInterfaces();
			for(ITypeBinding superInterface : superInterfaces) {
				if(superInterface.getQualifiedName().equals("java.lang.Cloneable")) {
					return implementedInterface;
				}
			}
		}
		return null;
	}

	private IVariableBinding staticFieldInitializer(VariableDeclaration fieldFragment) {
		if(fieldFragment.getInitializer() instanceof SimpleName) {
			SimpleName simpleName = (SimpleName)fieldFragment.getInitializer();
			IBinding binding = simpleName.resolveBinding();
			if(binding != null && binding.getKind() == IBinding.VARIABLE) {
				IVariableBinding variableBinding = (IVariableBinding)binding;
				if((variableBinding.getModifiers() & Modifier.STATIC) != 0) {
					return variableBinding;
				}
			}
		}
		return null;
	}

	private boolean variableBindingInExtractedFields(IVariableBinding variableBinding) {
		for(VariableDeclaration variableDeclaration : extractedFieldFragments) {
			if(variableDeclaration.resolveBinding().isEqualTo(variableBinding)) {
				return true;
			}
		}
		return false;
	}

	private void createExtractedClass(Set<VariableDeclaration> modifiedFieldsInNonExtractedMethods, Set<VariableDeclaration> accessedFieldsInNonExtractedMethods) {
		IContainer contextContainer = (IContainer)sourceFile.getParent();
		IFile extractedClassFile = null;
		if(contextContainer instanceof IProject) {
			IProject contextProject = (IProject)contextContainer;
			extractedClassFile = contextProject.getFile(extractedTypeName + ".java");
		}
		else if(contextContainer instanceof IFolder) {
			IFolder contextFolder = (IFolder)contextContainer;
			extractedClassFile = contextFolder.getFile(extractedTypeName + ".java");
		}
		ICompilationUnit extractedClassICompilationUnit = JavaCore.createCompilationUnitFrom(extractedClassFile);
		javaElementsToOpenInEditor.add(extractedClassICompilationUnit);
		ASTParser extractedClassParser = ASTParser.newParser(ASTReader.JLS);
		extractedClassParser.setKind(ASTParser.K_COMPILATION_UNIT);
		Document extractedClassDocument = new Document();
		extractedClassParser.setSource(extractedClassDocument.get().toCharArray());
		
		CompilationUnit extractedClassCompilationUnit = (CompilationUnit)extractedClassParser.createAST(null);
        AST extractedClassAST = extractedClassCompilationUnit.getAST();
        ASTRewrite extractedClassRewriter = ASTRewrite.create(extractedClassAST);
        ListRewrite extractedClassTypesRewrite = extractedClassRewriter.getListRewrite(extractedClassCompilationUnit, CompilationUnit.TYPES_PROPERTY);

        if(sourceCompilationUnit.getPackage() != null) {
        	extractedClassRewriter.set(extractedClassCompilationUnit, CompilationUnit.PACKAGE_PROPERTY, sourceCompilationUnit.getPackage(), null);
        }
        TypeDeclaration extractedClassTypeDeclaration = extractedClassAST.newTypeDeclaration();
        SimpleName extractedClassName = extractedClassAST.newSimpleName(extractedTypeName);
        extractedClassRewriter.set(extractedClassTypeDeclaration, TypeDeclaration.NAME_PROPERTY, extractedClassName, null);
