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

import gr.uom.java.ast.ASTInformationGenerator;
import gr.uom.java.ast.ASTReader;
import gr.uom.java.ast.AbstractMethodDeclaration;
import gr.uom.java.ast.ClassObject;
import gr.uom.java.ast.MethodObject;
import gr.uom.java.ast.decomposition.AbstractExpression;
import gr.uom.java.ast.decomposition.AbstractMethodFragment;
import gr.uom.java.ast.decomposition.AbstractStatement;
import gr.uom.java.ast.decomposition.CatchClauseObject;
import gr.uom.java.ast.decomposition.CompositeStatementObject;
import gr.uom.java.ast.decomposition.StatementObject;
import gr.uom.java.ast.decomposition.TryStatementObject;
import gr.uom.java.ast.decomposition.cfg.AbstractVariable;
import gr.uom.java.ast.decomposition.cfg.CFGBranchDoLoopNode;
import gr.uom.java.ast.decomposition.cfg.CFGNode;
import gr.uom.java.ast.decomposition.cfg.MethodCallAnalyzer;
import gr.uom.java.ast.decomposition.cfg.PDGBlockNode;
import gr.uom.java.ast.decomposition.cfg.PDGControlDependence;
import gr.uom.java.ast.decomposition.cfg.PDGExitNode;
import gr.uom.java.ast.decomposition.cfg.PDGNode;
import gr.uom.java.ast.decomposition.cfg.PDGTryNode;
import gr.uom.java.ast.decomposition.cfg.PlainVariable;
import gr.uom.java.ast.decomposition.cfg.mapping.CloneRefactoringType;
import gr.uom.java.ast.decomposition.cfg.mapping.CloneStructureNode;
import gr.uom.java.ast.decomposition.cfg.mapping.DivideAndConquerMatcher;
import gr.uom.java.ast.decomposition.cfg.mapping.NodeMapping;
import gr.uom.java.ast.decomposition.cfg.mapping.PDGElseGap;
import gr.uom.java.ast.decomposition.cfg.mapping.PDGElseMapping;
import gr.uom.java.ast.decomposition.cfg.mapping.PDGNodeGap;
import gr.uom.java.ast.decomposition.cfg.mapping.PDGNodeMapping;
import gr.uom.java.ast.decomposition.cfg.mapping.PreconditionExaminer;
import gr.uom.java.ast.decomposition.cfg.mapping.StatementCollector;
import gr.uom.java.ast.decomposition.cfg.mapping.VariableBindingKeyPair;
import gr.uom.java.ast.decomposition.cfg.mapping.precondition.DualExpressionPreconditionViolation;
import gr.uom.java.ast.decomposition.cfg.mapping.precondition.ExpressionPreconditionViolation;
import gr.uom.java.ast.decomposition.cfg.mapping.precondition.NotAllPossibleExecutionFlowsEndInReturnPreconditionViolation;
import gr.uom.java.ast.decomposition.cfg.mapping.precondition.PreconditionViolation;
import gr.uom.java.ast.decomposition.cfg.mapping.precondition.PreconditionViolationType;
import gr.uom.java.ast.decomposition.cfg.mapping.precondition.ReturnedVariablePreconditionViolation;
import gr.uom.java.ast.decomposition.cfg.mapping.precondition.StatementPreconditionViolation;
import gr.uom.java.ast.decomposition.cfg.mapping.precondition.UncommonSuperclassPreconditionViolation;
import gr.uom.java.ast.decomposition.cfg.mapping.precondition.ZeroMatchedStatementsPreconditionViolation;
import gr.uom.java.ast.decomposition.matching.ASTNodeDifference;
import gr.uom.java.ast.decomposition.matching.ASTNodeMatcher;
import gr.uom.java.ast.decomposition.matching.BindingSignature;
import gr.uom.java.ast.decomposition.matching.BindingSignaturePair;
import gr.uom.java.ast.decomposition.matching.Difference;
import gr.uom.java.ast.decomposition.matching.DifferenceType;
import gr.uom.java.ast.decomposition.matching.FieldAccessReplacedWithGetterInvocationDifference;
import gr.uom.java.ast.decomposition.matching.FieldAssignmentReplacedWithSetterInvocationDifference;
import gr.uom.java.ast.decomposition.matching.loop.EarliestStartPositionComparator;
import gr.uom.java.ast.util.ExpressionExtractor;
import gr.uom.java.ast.util.MethodDeclarationUtility;
import gr.uom.java.ast.util.SuperMethodInvocationVisitor;
import gr.uom.java.ast.util.ThrownExceptionVisitor;
import gr.uom.java.ast.util.TypeVisitor;

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

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.ITypeRoot;
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.ASTMatcher;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
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.CompilationUnit;
import org.eclipse.jdt.core.dom.DoStatement;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
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.ForStatement;
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.IfStatement;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.LabeledStatement;
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.ParameterizedType;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
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.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.SynchronizedStatement;
import org.eclipse.jdt.core.dom.ThisExpression;
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.WhileStatement;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.internal.corext.refactoring.util.JavaStatusContext;
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.RefactoringChangeDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.RefactoringStatusContext;
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 ExtractCloneRefactoring extends ExtractMethodFragmentRefactoring {
	private static final String GETTER_PREFIX = "get";
	private static final String SETTER_PREFIX = "set";
	private static final String ACCESSOR_SUFFIX = "2";
	private List<? extends DivideAndConquerMatcher> mappers;
	private DivideAndConquerMatcher mapper;
	private List<CompilationUnit> sourceCompilationUnits;
	private List<TypeDeclaration> sourceTypeDeclarations;
	private List<MethodDeclaration> sourceMethodDeclarations;
	private List<Set<VariableDeclaration>> fieldDeclarationsToBePulledUp;
	private List<Set<VariableDeclaration>> fieldDeclarationsToBeParameterized;
	private List<Set<VariableDeclaration>> accessedFieldDeclarationsToBeReplacedWithGetter;
	private List<Set<VariableDeclaration>> assignedFieldDeclarationsToBeReplacedWithSetter;
	private List<Set<MethodDeclaration>> methodDeclarationsToBePulledUp;
	private List<Set<LabeledStatement>> labeledStatementsToBeRemoved;
	private Map<ICompilationUnit, CompilationUnitChange> compilationUnitChanges;
	private Map<ICompilationUnit, CreateCompilationUnitChange> createCompilationUnitChanges;
	private Set<IJavaElement> javaElementsToOpenInEditor;
	private Set<PDGNodeMapping> sortedNodeMappings;
	private List<TreeSet<PDGNode>> removableStatements;
	private List<TreeSet<PDGNode>> remainingStatementsMovableBefore;
	private List<TreeSet<PDGNode>> remainingStatementsMovableAfter;
	private Map<VariableBindingKeyPair, ArrayList<VariableDeclaration>> originalPassedParameters;
	private Map<BindingSignaturePair, ASTNodeDifference> parameterizedDifferenceMap;
	private List<ArrayList<VariableDeclaration>> returnedVariables;
	private String extractedMethodName;
	private List<Set<MethodDeclaration>> constructorsToBeCopiedInSubclasses;
	private CloneInformation cloneInfo;
	private RefactoringStatus status = new RefactoringStatus();
	
	private class CloneInformation {
		private ICompilationUnit sourceICompilationUnit;
		private CompilationUnit sourceCompilationUnit;
		private TypeDeclaration sourceTypeDeclaration;
		private AST ast;
		private IFile file;
		private ASTRewrite sourceRewriter;
		private Document document;
		private Set<ITypeBinding> requiredImportTypeBindings;
		private ImportRewrite importRewrite;
		private ListRewrite methodBodyRewrite;
		private ListRewrite parameterRewrite;
		private List<ListRewrite> argumentRewriteList = new ArrayList<ListRewrite>();
		private List<ASTRewrite> originalMethodBodyRewriteList = new ArrayList<ASTRewrite>();
		private boolean superclassNotDirectlyInheritedFromRefactoredSubclasses;
		private boolean extractUtilityClass;
		private String intermediateClassName;
		private ITypeBinding[] intermediateClassTypeParameters;
		private IPackageBinding intermediateClassPackageBinding;
	}
	
	public ExtractCloneRefactoring(List<? extends DivideAndConquerMatcher> mappers) {
		super();
		this.mappers = mappers;
		this.mapper = mappers.get(0);
		if(this.mapper.getMethodName1().equals(this.mapper.getMethodName2())) {
			this.extractedMethodName = this.mapper.getMethodName1() + "Extracted";
		}
		else {
			this.extractedMethodName = this.mapper.getMethodName1();
		}
	}

	public List<? extends DivideAndConquerMatcher> getMappers() {
		return mappers;
	}

	private void initialize() {
		AbstractMethodDeclaration methodObject1 = this.mapper.getPDG1().getMethod();
		AbstractMethodDeclaration methodObject2 = this.mapper.getPDG2().getMethod();
		MethodDeclaration methodDeclaration1 = methodObject1.getMethodDeclaration();
		MethodDeclaration methodDeclaration2 = methodObject2.getMethodDeclaration();
		
		this.sourceCompilationUnits = new ArrayList<CompilationUnit>();
		this.sourceTypeDeclarations = new ArrayList<TypeDeclaration>();
		this.sourceMethodDeclarations = new ArrayList<MethodDeclaration>();
		this.removableStatements = new ArrayList<TreeSet<PDGNode>>();
		removableStatements.add(this.mapper.getRemovableNodesG1());
		removableStatements.add(this.mapper.getRemovableNodesG2());
		this.remainingStatementsMovableBefore = new ArrayList<TreeSet<PDGNode>>();
		remainingStatementsMovableBefore.add(this.mapper.getNonMappedPDGNodesG1MovableBefore());
		remainingStatementsMovableBefore.add(this.mapper.getNonMappedPDGNodesG2MovableBefore());
		this.remainingStatementsMovableAfter = new ArrayList<TreeSet<PDGNode>>();
		remainingStatementsMovableAfter.add(this.mapper.getNonMappedPDGNodesG1MovableAfter());
		remainingStatementsMovableAfter.add(this.mapper.getNonMappedPDGNodesG2MovableAfter());
		this.returnedVariables = new ArrayList<ArrayList<VariableDeclaration>>();
		returnedVariables.add(new ArrayList<VariableDeclaration>(this.mapper.getVariablesToBeReturnedG1()));
		returnedVariables.add(new ArrayList<VariableDeclaration>(this.mapper.getVariablesToBeReturnedG2()));
		this.fieldDeclarationsToBePulledUp = new ArrayList<Set<VariableDeclaration>>();
		this.fieldDeclarationsToBeParameterized = new ArrayList<Set<VariableDeclaration>>();
		this.accessedFieldDeclarationsToBeReplacedWithGetter = new ArrayList<Set<VariableDeclaration>>();
		this.assignedFieldDeclarationsToBeReplacedWithSetter = new ArrayList<Set<VariableDeclaration>>();
		this.methodDeclarationsToBePulledUp = new ArrayList<Set<MethodDeclaration>>();
		this.labeledStatementsToBeRemoved = new ArrayList<Set<LabeledStatement>>();
		//this.nodesToBePreservedInTheOriginalMethod = new ArrayList<TreeSet<PDGNode>>();
		for(int i=0; i<2; i++) {
			fieldDeclarationsToBePulledUp.add(new LinkedHashSet<VariableDeclaration>());
			fieldDeclarationsToBeParameterized.add(new LinkedHashSet<VariableDeclaration>());
			accessedFieldDeclarationsToBeReplacedWithGetter.add(new LinkedHashSet<VariableDeclaration>());
			assignedFieldDeclarationsToBeReplacedWithSetter.add(new LinkedHashSet<VariableDeclaration>());
			methodDeclarationsToBePulledUp.add(new LinkedHashSet<MethodDeclaration>());
			labeledStatementsToBeRemoved.add(new LinkedHashSet<LabeledStatement>());
			//nodesToBePreservedInTheOriginalMethod.add(new TreeSet<PDGNode>());
		}
		this.compilationUnitChanges = new LinkedHashMap<ICompilationUnit, CompilationUnitChange>();
		this.createCompilationUnitChanges = new LinkedHashMap<ICompilationUnit, CreateCompilationUnitChange>();
		this.javaElementsToOpenInEditor = new LinkedHashSet<IJavaElement>();
		
		this.sourceMethodDeclarations.add(methodDeclaration1);
		this.sourceMethodDeclarations.add(methodDeclaration2);
		if(methodDeclaration1.getParent() instanceof TypeDeclaration && methodDeclaration2.getParent() instanceof TypeDeclaration) { 
			this.sourceTypeDeclarations.add((TypeDeclaration)methodDeclaration1.getParent());
			this.sourceTypeDeclarations.add((TypeDeclaration)methodDeclaration2.getParent());
		}
		this.sourceCompilationUnits.add((CompilationUnit)methodDeclaration1.getRoot());
		this.sourceCompilationUnits.add((CompilationUnit)methodDeclaration2.getRoot());
		this.originalPassedParameters = new LinkedHashMap<VariableBindingKeyPair, ArrayList<VariableDeclaration>>();
		this.parameterizedDifferenceMap = new LinkedHashMap<BindingSignaturePair, ASTNodeDifference>();
		this.sortedNodeMappings = this.mapper.getMaximumStateWithMinimumDifferences().getSortedNodeMappings();
		for(PDGNodeMapping pdgNodeMapping : sortedNodeMappings) {
			PDGNode pdgNode = pdgNodeMapping.getNodeG1();
			CFGNode cfgNode = pdgNode.getCFGNode();
			if(cfgNode instanceof CFGBranchDoLoopNode) {
				CFGBranchDoLoopNode cfgDoLoopNode = (CFGBranchDoLoopNode)cfgNode;
				doLoopNodes.add(cfgDoLoopNode);
			}
		}
		/*StatementExtractor statementExtractor = new StatementExtractor();
		//examining the body of the first method declaration for try blocks
		List<Statement> tryStatements = statementExtractor.getTryStatements(methodDeclaration1.getBody());
		for(Statement tryStatement : tryStatements) {
			processTryStatement((TryStatement)tryStatement);
		}*/
		for(CompilationUnit sourceCompilationUnit : sourceCompilationUnits) {
			ICompilationUnit sourceICompilationUnit = (ICompilationUnit)sourceCompilationUnit.getJavaElement();
			MultiTextEdit sourceMultiTextEdit = new MultiTextEdit();
			CompilationUnitChange sourceCompilationUnitChange = new CompilationUnitChange("", sourceICompilationUnit);
			sourceCompilationUnitChange.setEdit(sourceMultiTextEdit);
			compilationUnitChanges.put(sourceICompilationUnit, sourceCompilationUnitChange);
		}
		this.cloneInfo = null;
		this.constructorsToBeCopiedInSubclasses = new ArrayList<Set<MethodDeclaration>>();
		for(int i=0; i<2; i++) {
			constructorsToBeCopiedInSubclasses.add(new LinkedHashSet<MethodDeclaration>());
		}
	}

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

	public DivideAndConquerMatcher getMapper() {
		return mapper;
	}

	public void setMapper(DivideAndConquerMatcher mapper) {
		this.mapper = mapper;
	}

	public String getExtractedMethodName() {
		return extractedMethodName;
	}

	public void setExtractedMethodName(String extractedMethodName) {
		this.extractedMethodName = extractedMethodName;
	}

	public void apply() {
		initialize();
		extractClone();
		if(status.getEntries().length == 0) {
			boolean bothClonesInTheSameCompilationUnit = sourceCompilationUnits.get(0).equals(sourceCompilationUnits.get(1));
			if(bothClonesInTheSameCompilationUnit) {
				modifySourceCompilationUnitImportDeclarations(sourceCompilationUnits.get(0), true);
			}
			for(int i=0; i<sourceCompilationUnits.size(); i++) {
				modifySourceClass(sourceCompilationUnits.get(i), sourceTypeDeclarations.get(i), fieldDeclarationsToBePulledUp.get(i), methodDeclarationsToBePulledUp.get(i),
						constructorsToBeCopiedInSubclasses.get(i), accessedFieldDeclarationsToBeReplacedWithGetter.get(i), assignedFieldDeclarationsToBeReplacedWithSetter.get(i));
				if(!bothClonesInTheSameCompilationUnit) {
					boolean cloneBelongsToTheCommonSuperclass = sourceCompilationUnits.get(i).getJavaElement().equals(cloneInfo.sourceCompilationUnit.getJavaElement());
					modifySourceCompilationUnitImportDeclarations(sourceCompilationUnits.get(i), cloneBelongsToTheCommonSuperclass);
				}
				modifySourceMethod(sourceCompilationUnits.get(i), sourceMethodDeclarations.get(i), removableStatements.get(i),
						remainingStatementsMovableBefore.get(i), remainingStatementsMovableAfter.get(i), returnedVariables.get(i), fieldDeclarationsToBeParameterized.get(i), i);
			}
			finalizeCloneExtraction();
		}
	}

	private boolean mappedNodesContainStatementReturningVariable(VariableDeclaration variableDeclaration1, VariableDeclaration variableDeclaration2) {
		for(PDGNodeMapping pdgNodeMapping : sortedNodeMappings) {
			PDGNode pdgNode1 = pdgNodeMapping.getNodeG1();
			PDGNode pdgNode2 = pdgNodeMapping.getNodeG2();
			if(pdgNode1 instanceof PDGExitNode && pdgNode2 instanceof PDGExitNode) {
				PDGExitNode exitNode1 = (PDGExitNode)pdgNode1;
				PDGExitNode exitNode2 = (PDGExitNode)pdgNode2;
				ReturnStatement returnStatement1 = (ReturnStatement)exitNode1.getASTStatement();
				ReturnStatement returnStatement2 = (ReturnStatement)exitNode2.getASTStatement();
				Expression returnedExpression1 = returnStatement1.getExpression();
				Expression returnedExpression2 = returnStatement2.getExpression();
				if(returnedExpression1 != null && returnedExpression2 != null) {
					if(returnedExpression1 instanceof SimpleName && returnedExpression2 instanceof SimpleName) {
						SimpleName simpleName1 = (SimpleName)returnedExpression1;
						SimpleName simpleName2 = (SimpleName)returnedExpression2;
						if(simpleName1.resolveBinding().isEqualTo(variableDeclaration1.getName().resolveBinding()) &&
								simpleName2.resolveBinding().isEqualTo(variableDeclaration2.getName().resolveBinding())) {
							return true;
						}
					}
				}
			}
		}
		return false;
	}

	private boolean mappedNodesContainStatementDeclaringVariable(VariableDeclaration variableDeclaration1, VariableDeclaration variableDeclaration2) {
		for(PDGNodeMapping pdgNodeMapping : sortedNodeMappings) {
			PDGNode pdgNode1 = pdgNodeMapping.getNodeG1();
			PDGNode pdgNode2 = pdgNodeMapping.getNodeG2();
			boolean node1DeclaresVariable = false;
			for(Iterator<AbstractVariable> declaredVariableIterator = pdgNode1.getDeclaredVariableIterator(); declaredVariableIterator.hasNext();) {
				AbstractVariable declaredVariable = declaredVariableIterator.next();
				if(declaredVariable.getVariableBindingKey().equals(variableDeclaration1.resolveBinding().getKey())) {
					node1DeclaresVariable = true;
					break;
				}
			}
			boolean node2DeclaresVariable = false;
			for(Iterator<AbstractVariable> declaredVariableIterator = pdgNode2.getDeclaredVariableIterator(); declaredVariableIterator.hasNext();) {
				AbstractVariable declaredVariable = declaredVariableIterator.next();
				if(declaredVariable.getVariableBindingKey().equals(variableDeclaration2.resolveBinding().getKey())) {
					node2DeclaresVariable = true;
					break;
				}
			}
			if(node1DeclaresVariable && node2DeclaresVariable) {
				return true;
			}
		}
		return false;
	}

	private boolean mappedNodesContainDifferentStatementsDeclaringVariables(VariableDeclaration variableDeclaration1, VariableDeclaration variableDeclaration2) {
		boolean variable1IsDeclared = false;
		boolean variable2IsDeclared = false;
		for(PDGNodeMapping pdgNodeMapping : sortedNodeMappings) {
			PDGNode pdgNode1 = pdgNodeMapping.getNodeG1();
			PDGNode pdgNode2 = pdgNodeMapping.getNodeG2();
			for(Iterator<AbstractVariable> declaredVariableIterator = pdgNode1.getDeclaredVariableIterator(); declaredVariableIterator.hasNext();) {
				AbstractVariable declaredVariable = declaredVariableIterator.next();
				if(declaredVariable.getVariableBindingKey().equals(variableDeclaration1.resolveBinding().getKey())) {
					variable1IsDeclared = true;
					break;
				}
			}
			for(Iterator<AbstractVariable> declaredVariableIterator = pdgNode2.getDeclaredVariableIterator(); declaredVariableIterator.hasNext();) {
				AbstractVariable declaredVariable = declaredVariableIterator.next();
				if(declaredVariable.getVariableBindingKey().equals(variableDeclaration2.resolveBinding().getKey())) {
					variable2IsDeclared = true;
					break;
				}
			}
		}
		if(variable1IsDeclared && variable2IsDeclared) {
			return true;
		}
		return false;
	}

	private BindingSignaturePair variableBelongsToParameterizedDifferences(VariableDeclaration variableDeclaration1, VariableDeclaration variableDeclaration2) {
		for(BindingSignaturePair pair : parameterizedDifferenceMap.keySet()) {
			if(pair.getSignature1().containsOnlyBinding(variableDeclaration1.resolveBinding().getKey()) &&
					pair.getSignature2().containsOnlyBinding(variableDeclaration2.resolveBinding().getKey())) {
				return pair;
			}
		}
		return null;
	}

	private boolean variableIsPassedAsCommonParameter(VariableDeclaration variableDeclaration1, VariableDeclaration variableDeclaration2) {
		for(VariableBindingKeyPair pair : mapper.getCommonPassedParameters().keySet()) {
			if(pair.getKey1().equals(variableDeclaration1.resolveBinding().getKey()) &&
					pair.getKey2().equals(variableDeclaration2.resolveBinding().getKey())) {
				return true;
			}
		}
		return false;
	}

	private boolean variableIsPassedAsCommonParameter(VariableDeclaration variableDeclaration) {
		for(VariableBindingKeyPair pair : mapper.getCommonPassedParameters().keySet()) {
			if(pair.getKey1().equals(variableDeclaration.resolveBinding().getKey()) ||
					pair.getKey2().equals(variableDeclaration.resolveBinding().getKey())) {
				return true;
			}
		}
		return false;
	}

	private boolean variableIsDeclaredInMappedNodes(VariableDeclaration variableDeclaration, Set<PDGNode> mappedNodes) {
		PlainVariable plainVariable = new PlainVariable(variableDeclaration);
		for(PDGNode mappedNode : mappedNodes) {
			if(mappedNode.declaresLocalVariable(plainVariable)) {
				return true;
			}
		}
		return false;
	}

	private Set<VariableDeclaration> getLocallyAccessedFields(Set<AbstractVariable> accessedFields, TypeDeclaration typeDeclaration) {
		Set<VariableDeclaration> accessedLocalFields = new LinkedHashSet<VariableDeclaration>();
		for(AbstractVariable variable : accessedFields) {
			VariableDeclaration fieldDeclaration = RefactoringUtility.findFieldDeclaration(variable, typeDeclaration);
			if(fieldDeclaration != null) {
				accessedLocalFields.add(fieldDeclaration);
			}
		}
		return accessedLocalFields;
	}

	private boolean containsImportWithNameClash(CompilationUnit cunit, ITypeBinding typeBinding) {
		List<ImportDeclaration> imports = cunit.imports();
		for(ImportDeclaration importDeclaration : imports) {
			IBinding importBinding = importDeclaration.resolveBinding();
			if(importBinding.getKind() == IBinding.TYPE) {
				ITypeBinding importTypeBinding = (ITypeBinding)importBinding;
				if(importTypeBinding.getName().equals(typeBinding.getName()) && !importTypeBinding.isEqualTo(typeBinding)) {
					return true;
				}
			}
		}
		return false;
	}

	private void extractClone() {
		this.cloneInfo = new CloneInformation();
		Set<ITypeBinding> requiredImportTypeBindings = new LinkedHashSet<ITypeBinding>();
		ITypeBinding commonSuperTypeOfSourceTypeDeclarations = null;
		ITypeBinding declaringClass1 = sourceTypeDeclarations.get(0).resolveBinding().getDeclaringClass();
		ITypeBinding declaringClass2 = sourceTypeDeclarations.get(1).resolveBinding().getDeclaringClass();
		if(sourceTypeDeclarations.get(0).resolveBinding().isEqualTo(sourceTypeDeclarations.get(1).resolveBinding()) &&
				sourceTypeDeclarations.get(0).resolveBinding().getQualifiedName().equals(sourceTypeDeclarations.get(1).resolveBinding().getQualifiedName())) {
			cloneInfo.sourceCompilationUnit = sourceCompilationUnits.get(0);
			cloneInfo.sourceICompilationUnit = (ICompilationUnit)cloneInfo.sourceCompilationUnit.getJavaElement();
			cloneInfo.sourceTypeDeclaration = sourceTypeDeclarations.get(0);
			cloneInfo.sourceRewriter = ASTRewrite.create(cloneInfo.sourceTypeDeclaration.getAST());
			cloneInfo.ast = cloneInfo.sourceTypeDeclaration.getAST();
		}
		else if(declaringClass1 != null && declaringClass2 != null && declaringClass1.isEqualTo(declaringClass2) && declaringClass1.getQualifiedName().equals(declaringClass2.getQualifiedName())) {
			cloneInfo.sourceCompilationUnit = sourceCompilationUnits.get(0);
			cloneInfo.sourceICompilationUnit = (ICompilationUnit)cloneInfo.sourceCompilationUnit.getJavaElement();
			List<AbstractTypeDeclaration> topLevelTypeDeclarations = cloneInfo.sourceCompilationUnit.types();
			List<AbstractTypeDeclaration> allTypeDeclarations = new ArrayList<AbstractTypeDeclaration>();
			for(AbstractTypeDeclaration abstractTypeDeclaration : topLevelTypeDeclarations) {
				if(abstractTypeDeclaration instanceof TypeDeclaration) {
					TypeDeclaration topLevelTypeDeclaration = (TypeDeclaration)abstractTypeDeclaration;
					allTypeDeclarations.add(topLevelTypeDeclaration);
					allTypeDeclarations.addAll(ASTReader.getRecursivelyInnerTypes(topLevelTypeDeclaration));
				}
			}
			for(AbstractTypeDeclaration abstractTypeDeclaration : allTypeDeclarations) {
				if(abstractTypeDeclaration instanceof TypeDeclaration) {
					TypeDeclaration typeDeclaration = (TypeDeclaration)abstractTypeDeclaration;
					cloneInfo.sourceTypeDeclaration = typeDeclaration;
					cloneInfo.sourceRewriter = ASTRewrite.create(cloneInfo.sourceTypeDeclaration.getAST());
					cloneInfo.ast = cloneInfo.sourceTypeDeclaration.getAST();
					break;
				}
			}
		}
		else {
			//check if they have a common superclass
			ITypeBinding typeBinding1 = sourceTypeDeclarations.get(0).resolveBinding();
			ITypeBinding typeBinding2 = sourceTypeDeclarations.get(1).resolveBinding();
			commonSuperTypeOfSourceTypeDeclarations = ASTNodeMatcher.commonSuperType(typeBinding1, typeBinding2);
			if(commonSuperTypeOfSourceTypeDeclarations != null) {
				if(mapper.getCloneRefactoringType().equals(CloneRefactoringType.PULL_UP_TO_EXISTING_SUPERCLASS)) {
					IJavaElement javaElement = commonSuperTypeOfSourceTypeDeclarations.getJavaElement();
					javaElementsToOpenInEditor.add(javaElement);
					//special handling for the case the common superclass is an inner class
					IJavaElement parent = javaElement.getParent();
					while(!(parent instanceof ICompilationUnit)) {
						parent = parent.getParent();
					}
					ICompilationUnit iCompilationUnit = (ICompilationUnit)parent;
					ASTParser parser = ASTParser.newParser(ASTReader.JLS);
					parser.setKind(ASTParser.K_COMPILATION_UNIT);
					parser.setSource(iCompilationUnit);
					parser.setResolveBindings(true); // we need bindings later on
					CompilationUnit compilationUnit = (CompilationUnit)parser.createAST(null);
					List<AbstractTypeDeclaration> topLevelTypeDeclarations = compilationUnit.types();
					List<AbstractTypeDeclaration> allTypeDeclarations = new ArrayList<AbstractTypeDeclaration>();
					for(AbstractTypeDeclaration abstractTypeDeclaration : topLevelTypeDeclarations) {
						if(abstractTypeDeclaration instanceof TypeDeclaration) {
							TypeDeclaration topLevelTypeDeclaration = (TypeDeclaration)abstractTypeDeclaration;
							allTypeDeclarations.add(topLevelTypeDeclaration);
							allTypeDeclarations.addAll(ASTReader.getRecursivelyInnerTypes(topLevelTypeDeclaration));
						}
					}
					for(AbstractTypeDeclaration abstractTypeDeclaration : allTypeDeclarations) {
						if(abstractTypeDeclaration instanceof TypeDeclaration) {
							TypeDeclaration typeDeclaration = (TypeDeclaration)abstractTypeDeclaration;
							if(typeDeclaration.resolveBinding().isEqualTo(commonSuperTypeOfSourceTypeDeclarations)) {
								cloneInfo.sourceCompilationUnit = compilationUnit;
								cloneInfo.sourceICompilationUnit = iCompilationUnit;
								cloneInfo.sourceTypeDeclaration = typeDeclaration;
								cloneInfo.sourceRewriter = ASTRewrite.create(cloneInfo.sourceTypeDeclaration.getAST());
								cloneInfo.ast = cloneInfo.sourceTypeDeclaration.getAST();
								cloneInfo.superclassNotDirectlyInheritedFromRefactoredSubclasses =
										!superclassDirectlyInheritedFromRefactoredSubclasses(commonSuperTypeOfSourceTypeDeclarations, typeBinding1, typeBinding2);
								break;
							}
						}
					}
					MultiTextEdit multiTextEdit = new MultiTextEdit();
					CompilationUnitChange compilationUnitChange = new CompilationUnitChange("", iCompilationUnit);
					compilationUnitChange.setEdit(multiTextEdit);
					compilationUnitChanges.put(iCompilationUnit, compilationUnitChange);
				}
				else {
					//create an intermediate superclass or a utility class
					if(mapper.getCloneRefactoringType().equals(CloneRefactoringType.EXTRACT_STATIC_METHOD_TO_NEW_UTILITY_CLASS)) {
						cloneInfo.extractUtilityClass = true;
					}
					if(cloneInfo.extractUtilityClass) {
						cloneInfo.intermediateClassName = "Utility";
					}
					else {
						cloneInfo.intermediateClassName = "Intermediate" + commonSuperTypeOfSourceTypeDeclarations.getTypeDeclaration().getName();
					}
					ClassObject commonSuperType = ASTReader.getSystemObject().getClassObject(commonSuperTypeOfSourceTypeDeclarations.getQualifiedName());
					CompilationUnit compilationUnit = null;
					PackageDeclaration package1 = sourceCompilationUnits.get(0).getPackage();
					PackageDeclaration package2 = sourceCompilationUnits.get(1).getPackage();
					if(package1 != null && package2 != null && package1.resolveBinding().isEqualTo(package2.resolveBinding())) {
						compilationUnit = sourceCompilationUnits.get(0);
					}
					else if(package1 == null && package2 == null) {
						compilationUnit = sourceCompilationUnits.get(0);
					}
					else if(commonSuperType != null) {
						compilationUnit = RefactoringUtility.findCompilationUnit(commonSuperType.getAbstractTypeDeclaration());
					}
					else {
						compilationUnit = sourceCompilationUnits.get(0);
					}
					if(compilationUnit.getPackage() != null) {
						cloneInfo.intermediateClassPackageBinding = compilationUnit.getPackage().resolveBinding();
					}
					ICompilationUnit iCompilationUnit = (ICompilationUnit)compilationUnit.getJavaElement();
					IContainer container = (IContainer)iCompilationUnit.getResource().getParent();
					if(container instanceof IProject) {
						IProject contextProject = (IProject)container;
						cloneInfo.file = contextProject.getFile(cloneInfo.intermediateClassName + ".java");
					}
					else if(container instanceof IFolder) {
