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

import gr.uom.java.ast.decomposition.cfg.CFGBranchDoLoopNode;
import gr.uom.java.ast.decomposition.cfg.GraphEdge;
import gr.uom.java.ast.decomposition.cfg.PDGControlDependence;
import gr.uom.java.ast.decomposition.cfg.PDGControlPredicateNode;
import gr.uom.java.ast.decomposition.cfg.PDGDependence;
import gr.uom.java.ast.decomposition.cfg.PDGNode;
import gr.uom.java.ast.util.ExpressionExtractor;
import gr.uom.java.ast.util.TypeVisitor;

import java.util.ArrayList;
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 org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.AssertStatement;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BreakStatement;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.CharacterLiteral;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.ContinueStatement;
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.ForStatement;
import org.eclipse.jdt.core.dom.IMethodBinding;
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.LabeledStatement;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.ReturnStatement;
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.SwitchCase;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.SynchronizedStatement;
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.UnionType;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
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.ListRewrite;
import org.eclipse.ltk.core.refactoring.Refactoring;

public abstract class ExtractMethodFragmentRefactoring extends Refactoring {

	protected Set<TryStatement> tryStatementsToBeRemoved;
	protected Set<TryStatement> tryStatementsToBeCopied;
	protected Map<TryStatement, ListRewrite> tryStatementBodyRewriteMap;
	protected List<CFGBranchDoLoopNode> doLoopNodes;
	protected Set<LabeledStatement> labeledStatementsToBeRemoved;

	public ExtractMethodFragmentRefactoring() {
		this.tryStatementsToBeRemoved = new LinkedHashSet<TryStatement>();
		this.tryStatementsToBeCopied = new LinkedHashSet<TryStatement>();
		this.tryStatementBodyRewriteMap = new LinkedHashMap<TryStatement, ListRewrite>();
		this.doLoopNodes = new ArrayList<CFGBranchDoLoopNode>();
		this.labeledStatementsToBeRemoved = new LinkedHashSet<LabeledStatement>();
	}

	protected List<Statement> getStatements(Statement statement) {
		List<Statement> statementList = new ArrayList<Statement>();
		if(statement instanceof Block) {
			Block block = (Block)statement;
			List<Statement> blockStatements = block.statements();
			for(Statement blockStatement : blockStatements)
				statementList.addAll(getStatements(blockStatement));
		}
		else if(statement instanceof IfStatement) {
			IfStatement ifStatement = (IfStatement)statement;
			statementList.add(ifStatement);
			statementList.addAll(getStatements(ifStatement.getThenStatement()));
			if(ifStatement.getElseStatement() != null) {
				statementList.addAll(getStatements(ifStatement.getElseStatement()));
			}
		}
		else if(statement instanceof ForStatement) {
			ForStatement forStatement = (ForStatement)statement;
			statementList.add(forStatement);
			statementList.addAll(getStatements(forStatement.getBody()));
		}
		else if(statement instanceof EnhancedForStatement) {
			EnhancedForStatement enhancedForStatement = (EnhancedForStatement)statement;
			statementList.add(enhancedForStatement);
			statementList.addAll(getStatements(enhancedForStatement.getBody()));
		}
		else if(statement instanceof WhileStatement) {
			WhileStatement whileStatement = (WhileStatement)statement;
			statementList.add(whileStatement);
			statementList.addAll(getStatements(whileStatement.getBody()));
		}
		else if(statement instanceof DoStatement) {
			DoStatement doStatement = (DoStatement)statement;
			statementList.add(doStatement);
			statementList.addAll(getStatements(doStatement.getBody()));
		}
		else if(statement instanceof ExpressionStatement) {
			ExpressionStatement expressionStatement = (ExpressionStatement)statement;
			statementList.add(expressionStatement);
		}
		else if(statement instanceof SwitchStatement) {
			SwitchStatement switchStatement = (SwitchStatement)statement;
			statementList.add(switchStatement);
			List<Statement> statements = switchStatement.statements();
			for(Statement statement2 : statements)
				statementList.addAll(getStatements(statement2));
		}
		else if(statement instanceof SwitchCase) {
			SwitchCase switchCase = (SwitchCase)statement;
			statementList.add(switchCase);
		}
		else if(statement instanceof AssertStatement) {
			AssertStatement assertStatement = (AssertStatement)statement;
			statementList.add(assertStatement);
		}
		else if(statement instanceof LabeledStatement) {
			LabeledStatement labeledStatement = (LabeledStatement)statement;
			//handling of LabeledStatement
			statementList.addAll(getStatements(labeledStatement.getBody()));
		}
		else if(statement instanceof ReturnStatement) {
			ReturnStatement returnStatement = (ReturnStatement)statement;
			statementList.add(returnStatement);
		}
		else if(statement instanceof SynchronizedStatement) {
			SynchronizedStatement synchronizedStatement = (SynchronizedStatement)statement;
			//handling of SynchronizedStatement
			statementList.addAll(getStatements(synchronizedStatement.getBody()));
		}
		else if(statement instanceof ThrowStatement) {
			ThrowStatement throwStatement = (ThrowStatement)statement;
			statementList.add(throwStatement);
		}
		else if(statement instanceof TryStatement) {
			TryStatement tryStatement = (TryStatement)statement;
			statementList.addAll(getStatements(tryStatement.getBody()));
			/*List<CatchClause> catchClauses = tryStatement.catchClauses();
			for(CatchClause catchClause : catchClauses) {
				statementList.addAll(getStatements(catchClause.getBody()));
			}
			Block finallyBlock = tryStatement.getFinally();
			if(finallyBlock != null)
				statementList.addAll(getStatements(finallyBlock));*/
		}
		else if(statement instanceof VariableDeclarationStatement) {
			VariableDeclarationStatement variableDeclarationStatement = (VariableDeclarationStatement)statement;
			statementList.add(variableDeclarationStatement);
		}
		else if(statement instanceof ConstructorInvocation) {
			ConstructorInvocation constructorInvocation = (ConstructorInvocation)statement;
			statementList.add(constructorInvocation);
		}
		else if(statement instanceof SuperConstructorInvocation) {
			SuperConstructorInvocation superConstructorInvocation = (SuperConstructorInvocation)statement;
			statementList.add(superConstructorInvocation);
		}
		else if(statement instanceof BreakStatement) {
			BreakStatement breakStatement = (BreakStatement)statement;
			statementList.add(breakStatement);
		}
		else if(statement instanceof ContinueStatement) {
			ContinueStatement continueStatement = (ContinueStatement)statement;
			statementList.add(continueStatement);
		}
		return statementList;
	}

	protected Set<ITypeBinding> getThrownExceptionTypes(Statement statement) {
		ExpressionExtractor expressionExtractor = new ExpressionExtractor();
		List<Expression> methodInvocations = new ArrayList<Expression>();
		List<Expression> classInstanceCreations = new ArrayList<Expression>();
		if(statement instanceof IfStatement) {
			IfStatement ifStatement = (IfStatement)statement;
			Expression ifExpression = ifStatement.getExpression();
			methodInvocations.addAll(expressionExtractor.getMethodInvocations(ifExpression));
			classInstanceCreations.addAll(expressionExtractor.getClassInstanceCreations(ifExpression));
		}
		else if(statement instanceof WhileStatement) {
			WhileStatement whileStatement = (WhileStatement)statement;
			Expression whileExpression = whileStatement.getExpression();
			methodInvocations.addAll(expressionExtractor.getMethodInvocations(whileExpression));
			classInstanceCreations.addAll(expressionExtractor.getClassInstanceCreations(whileExpression));
		}
		else if(statement instanceof ForStatement) {
			ForStatement forStatement = (ForStatement)statement;
			List<Expression> initializers = forStatement.initializers();
			for(Expression expression : initializers) {
				methodInvocations.addAll(expressionExtractor.getMethodInvocations(expression));
				classInstanceCreations.addAll(expressionExtractor.getClassInstanceCreations(expression));
			}
			Expression forExpression = forStatement.getExpression();
			if(forExpression != null) {
				methodInvocations.addAll(expressionExtractor.getMethodInvocations(forExpression));
				classInstanceCreations.addAll(expressionExtractor.getClassInstanceCreations(forExpression));
			}
			List<Expression> updaters = forStatement.updaters();
			for(Expression expression : updaters) {
				methodInvocations.addAll(expressionExtractor.getMethodInvocations(expression));
				classInstanceCreations.addAll(expressionExtractor.getClassInstanceCreations(expression));
			}
		}
		else if(statement instanceof EnhancedForStatement) {
			EnhancedForStatement enhancedForStatement = (EnhancedForStatement)statement;
			Expression expression = enhancedForStatement.getExpression();
			methodInvocations.addAll(expressionExtractor.getMethodInvocations(expression));
			classInstanceCreations.addAll(expressionExtractor.getClassInstanceCreations(expression));
		}
		else if(statement instanceof DoStatement) {
			DoStatement doStatement = (DoStatement)statement;
			Expression doExpression = doStatement.getExpression();
			methodInvocations.addAll(expressionExtractor.getMethodInvocations(doExpression));
			classInstanceCreations.addAll(expressionExtractor.getClassInstanceCreations(doExpression));
		}
		else if(statement instanceof SwitchStatement) {
			SwitchStatement switchStatement = (SwitchStatement)statement;
			Expression switchExpression = switchStatement.getExpression();
			methodInvocations.addAll(expressionExtractor.getMethodInvocations(switchExpression));
			classInstanceCreations.addAll(expressionExtractor.getClassInstanceCreations(switchExpression));
		}
		else if(statement instanceof TryStatement) {
			
		}
		else {
			methodInvocations.addAll(expressionExtractor.getMethodInvocations(statement));
			classInstanceCreations.addAll(expressionExtractor.getClassInstanceCreations(statement));
		}
		Set<ITypeBinding> thrownExceptionTypes = new LinkedHashSet<ITypeBinding>();
		for(Expression expression : methodInvocations) {
			TryStatement surroundingTryBlock = surroundingTryBlock(expression);
			if(expression instanceof MethodInvocation) {
				MethodInvocation methodInvocation = (MethodInvocation)expression;
				IMethodBinding methodInvocationBinding = methodInvocation.resolveMethodBinding();
				ITypeBinding[] exceptionTypes = methodInvocationBinding.getExceptionTypes();
				for(ITypeBinding typeBinding : exceptionTypes) {
					if(surroundingTryBlock == null || (surroundingTryBlock != null && !isNestedUnderAnonymousClassDeclaration(surroundingTryBlock))) {
						thrownExceptionTypes.add(typeBinding);
					}
				}
			}
			else if(expression instanceof SuperMethodInvocation) {
				SuperMethodInvocation superMethodInvocation = (SuperMethodInvocation)expression;
				IMethodBinding methodInvocationBinding = superMethodInvocation.resolveMethodBinding();
				ITypeBinding[] exceptionTypes = methodInvocationBinding.getExceptionTypes();
				for(ITypeBinding typeBinding : exceptionTypes) {
					if(surroundingTryBlock == null || (surroundingTryBlock != null && !isNestedUnderAnonymousClassDeclaration(surroundingTryBlock))) {
						thrownExceptionTypes.add(typeBinding);
					}
				}
			}
		}
		for(Expression expression : classInstanceCreations) {
			TryStatement surroundingTryBlock = surroundingTryBlock(expression);
			ClassInstanceCreation classInstanceCreation = (ClassInstanceCreation)expression;
			IMethodBinding constructorBinding = classInstanceCreation.resolveConstructorBinding();
			ITypeBinding[] exceptionTypes = constructorBinding.getExceptionTypes();
			for(ITypeBinding typeBinding : exceptionTypes) {
				if(surroundingTryBlock == null || (surroundingTryBlock != null && !isNestedUnderAnonymousClassDeclaration(surroundingTryBlock))) {
					thrownExceptionTypes.add(typeBinding);
				}
			}
		}
		if(statement instanceof ThrowStatement) {
			TryStatement surroundingTryBlock = surroundingTryBlock(statement);
			ThrowStatement throwStatement = (ThrowStatement)statement;
			TypeVisitor typeVisitor = new TypeVisitor();
			throwStatement.accept(typeVisitor);
			if(surroundingTryBlock == null || (surroundingTryBlock != null && !isNestedUnderAnonymousClassDeclaration(surroundingTryBlock))) {
				thrownExceptionTypes.addAll(typeVisitor.getTypeBindings());
			}
		}
		return thrownExceptionTypes;
	}

	private boolean isNestedUnderAnonymousClassDeclaration(ASTNode node) {
		ASTNode parent = node.getParent();
		while(parent != null) {
			if(parent instanceof AnonymousClassDeclaration) {
				return true;
			}
			parent = parent.getParent();
		}
		return false;
	}

	protected Statement processPredicateNode(PDGControlPredicateNode predicateNode, AST ast,
			ASTRewrite sourceRewriter, List<PDGNode> sliceNodes) {
		Statement oldPredicateStatement = predicateNode.getASTStatement();
		sliceNodes.remove(predicateNode);
		Statement newPredicateStatement = null;
		if(oldPredicateStatement instanceof IfStatement) {
			IfStatement oldIfStatement = (IfStatement)oldPredicateStatement;
			IfStatement newIfStatement = ast.newIfStatement();
			newPredicateStatement = newIfStatement;
			sourceRewriter.set(newIfStatement, IfStatement.EXPRESSION_PROPERTY, oldIfStatement.getExpression(), null);
			Iterator<GraphEdge> outgoingDependenceIterator = predicateNode.getOutgoingDependenceIterator();
			List<PDGNode> trueControlDependentChildren = new ArrayList<PDGNode>();
			List<PDGNode> falseControlDependentChildren = new ArrayList<PDGNode>();
			while(outgoingDependenceIterator.hasNext()) {
				PDGDependence dependence = (PDGDependence)outgoingDependenceIterator.next();
				if(dependence instanceof PDGControlDependence) {
					PDGControlDependence controlDependence = (PDGControlDependence)dependence;
					PDGNode dstPDGNode = (PDGNode)controlDependence.getDst();
					if(sliceNodes.contains(dstPDGNode)) {
						if(controlDependence.isTrueControlDependence()) {
							trueControlDependentChildren.add(dstPDGNode);
						}
						else if(controlDependence.isFalseControlDependence()) {
							falseControlDependentChildren.add(dstPDGNode);
						}
					}
				}
			}
			if(oldIfStatement.getThenStatement() instanceof Block || trueControlDependentChildren.size() > 1) {
				Block thenBlock = ast.newBlock();
				ListRewrite thenBodyRewrite = sourceRewriter.getListRewrite(thenBlock, Block.STATEMENTS_PROPERTY);
				for(PDGNode dstPDGNode : trueControlDependentChildren) {
					ListRewrite listRewrite = thenBodyRewrite;
					listRewrite = createTryStatementIfNeeded(sourceRewriter, ast, listRewrite, dstPDGNode);
					if(dstPDGNode instanceof PDGControlPredicateNode) {
						PDGControlPredicateNode dstPredicateNode = (PDGControlPredicateNode)dstPDGNode;
						listRewrite.insertLast(processPredicateNode(dstPredicateNode, ast, sourceRewriter, sliceNodes), null);
					}
					else {
						processStatementNode(listRewrite, dstPDGNode, ast, sourceRewriter);
						sliceNodes.remove(dstPDGNode);
					}
				}
				sourceRewriter.set(newIfStatement, IfStatement.THEN_STATEMENT_PROPERTY, thenBlock, null);
			}
			else if(trueControlDependentChildren.size() == 1) {
				PDGNode dstPDGNode = trueControlDependentChildren.get(0);
				if(dstPDGNode instanceof PDGControlPredicateNode) {
					PDGControlPredicateNode dstPredicateNode = (PDGControlPredicateNode)dstPDGNode;
					sourceRewriter.set(newIfStatement, IfStatement.THEN_STATEMENT_PROPERTY, processPredicateNode(dstPredicateNode, ast, sourceRewriter, sliceNodes), null);
				}
				else {
					sourceRewriter.set(newIfStatement, IfStatement.THEN_STATEMENT_PROPERTY, dstPDGNode.getASTStatement(), null);
					sliceNodes.remove(dstPDGNode);
				}
			}
			if(oldIfStatement.getElseStatement() instanceof Block || falseControlDependentChildren.size() > 1) {
				Block elseBlock = ast.newBlock();
				ListRewrite elseBodyRewrite = sourceRewriter.getListRewrite(elseBlock, Block.STATEMENTS_PROPERTY);
				for(PDGNode dstPDGNode : falseControlDependentChildren) {
					ListRewrite listRewrite = elseBodyRewrite;
					listRewrite = createTryStatementIfNeeded(sourceRewriter, ast, listRewrite, dstPDGNode);
					if(dstPDGNode instanceof PDGControlPredicateNode) {
						PDGControlPredicateNode dstPredicateNode = (PDGControlPredicateNode)dstPDGNode;
						listRewrite.insertLast(processPredicateNode(dstPredicateNode, ast, sourceRewriter, sliceNodes), null);
					}
					else {
						processStatementNode(listRewrite, dstPDGNode, ast, sourceRewriter);
						sliceNodes.remove(dstPDGNode);
					}
				}
				sourceRewriter.set(newIfStatement, IfStatement.ELSE_STATEMENT_PROPERTY, elseBlock, null);
			}
			else if(falseControlDependentChildren.size() == 1) {
				PDGNode dstPDGNode = falseControlDependentChildren.get(0);
				if(dstPDGNode instanceof PDGControlPredicateNode) {
					PDGControlPredicateNode dstPredicateNode = (PDGControlPredicateNode)dstPDGNode;
					sourceRewriter.set(newIfStatement, IfStatement.ELSE_STATEMENT_PROPERTY, processPredicateNode(dstPredicateNode, ast, sourceRewriter, sliceNodes), null);
				}
				else {
					sourceRewriter.set(newIfStatement, IfStatement.ELSE_STATEMENT_PROPERTY, dstPDGNode.getASTStatement(), null);
					sliceNodes.remove(dstPDGNode);
				}
			}	
		}
		else if(oldPredicateStatement instanceof SwitchStatement) {
			SwitchStatement oldSwitchStatement = (SwitchStatement)oldPredicateStatement;
			SwitchStatement newSwitchStatement = ast.newSwitchStatement();
			newPredicateStatement = newSwitchStatement;
			sourceRewriter.set(newSwitchStatement, SwitchStatement.EXPRESSION_PROPERTY, oldSwitchStatement.getExpression(), null);
			ListRewrite switchStatementsRewrite = sourceRewriter.getListRewrite(newSwitchStatement, SwitchStatement.STATEMENTS_PROPERTY);
			Iterator<GraphEdge> outgoingDependenceIterator = predicateNode.getOutgoingDependenceIterator();
			while(outgoingDependenceIterator.hasNext()) {
				ListRewrite bodyRewrite = switchStatementsRewrite;
				PDGDependence dependence = (PDGDependence)outgoingDependenceIterator.next();
				if(dependence instanceof PDGControlDependence) {
					PDGControlDependence controlDependence = (PDGControlDependence)dependence;
					PDGNode dstPDGNode = (PDGNode)controlDependence.getDst();
					if(sliceNodes.contains(dstPDGNode)) {
						bodyRewrite = createTryStatementIfNeeded(sourceRewriter, ast, bodyRewrite, dstPDGNode);
						if(dstPDGNode instanceof PDGControlPredicateNode) {
							PDGControlPredicateNode dstPredicateNode = (PDGControlPredicateNode)dstPDGNode;
							bodyRewrite.insertLast(processPredicateNode(dstPredicateNode, ast, sourceRewriter, sliceNodes), null);
						}
						else {
							processStatementNode(bodyRewrite, dstPDGNode, ast, sourceRewriter);
							sliceNodes.remove(dstPDGNode);
						}
					}
				}
			}
		}
		else {
			Block loopBlock = ast.newBlock();
			ListRewrite loopBodyRewrite = sourceRewriter.getListRewrite(loopBlock, Block.STATEMENTS_PROPERTY);
			Iterator<GraphEdge> outgoingDependenceIterator = predicateNode.getOutgoingDependenceIterator();
			while(outgoingDependenceIterator.hasNext()) {
				ListRewrite bodyRewrite = loopBodyRewrite;
				PDGDependence dependence = (PDGDependence)outgoingDependenceIterator.next();
				if(dependence instanceof PDGControlDependence) {
					PDGControlDependence controlDependence = (PDGControlDependence)dependence;
					PDGNode dstPDGNode = (PDGNode)controlDependence.getDst();
					if(sliceNodes.contains(dstPDGNode)) {
						bodyRewrite = createTryStatementIfNeeded(sourceRewriter, ast, bodyRewrite, dstPDGNode);
						if(dstPDGNode instanceof PDGControlPredicateNode) {
							PDGControlPredicateNode dstPredicateNode = (PDGControlPredicateNode)dstPDGNode;
							bodyRewrite.insertLast(processPredicateNode(dstPredicateNode, ast, sourceRewriter, sliceNodes), null);
						}
						else {
							processStatementNode(bodyRewrite, dstPDGNode, ast, sourceRewriter);
							sliceNodes.remove(dstPDGNode);
						}
					}
				}
			}
			if(oldPredicateStatement instanceof WhileStatement) {
				WhileStatement oldWhileStatement = (WhileStatement)oldPredicateStatement;
				WhileStatement newWhileStatement = ast.newWhileStatement();
				newPredicateStatement = newWhileStatement;
				sourceRewriter.set(newWhileStatement, WhileStatement.EXPRESSION_PROPERTY, oldWhileStatement.getExpression(), null);
				sourceRewriter.set(newWhileStatement, WhileStatement.BODY_PROPERTY, loopBlock, null);
			}
			else if(oldPredicateStatement instanceof ForStatement) {
				ForStatement oldForStatement = (ForStatement)oldPredicateStatement;
				ForStatement newForStatement = ast.newForStatement();
				newPredicateStatement = newForStatement;
				sourceRewriter.set(newForStatement, ForStatement.EXPRESSION_PROPERTY, oldForStatement.getExpression(), null);
				ListRewrite initializerRewrite = sourceRewriter.getListRewrite(newForStatement, ForStatement.INITIALIZERS_PROPERTY);
				List<Expression> initializers = oldForStatement.initializers();
				for(Expression expression : initializers)
					initializerRewrite.insertLast(expression, null);
				ListRewrite updaterRewrite = sourceRewriter.getListRewrite(newForStatement, ForStatement.UPDATERS_PROPERTY);
				List<Expression> updaters = oldForStatement.updaters();
				for(Expression expression : updaters)
					updaterRewrite.insertLast(expression, null);
				sourceRewriter.set(newForStatement, ForStatement.BODY_PROPERTY, loopBlock, null);
			}
			else if(oldPredicateStatement instanceof EnhancedForStatement) {
				EnhancedForStatement oldEnhancedForStatement = (EnhancedForStatement)oldPredicateStatement;
				EnhancedForStatement newEnhancedForStatement = ast.newEnhancedForStatement();
				newPredicateStatement = newEnhancedForStatement;
				sourceRewriter.set(newEnhancedForStatement, EnhancedForStatement.PARAMETER_PROPERTY, oldEnhancedForStatement.getParameter(), null);
				sourceRewriter.set(newEnhancedForStatement, EnhancedForStatement.EXPRESSION_PROPERTY, oldEnhancedForStatement.getExpression(), null);
				sourceRewriter.set(newEnhancedForStatement, EnhancedForStatement.BODY_PROPERTY, loopBlock, null);
			}
			else if(oldPredicateStatement instanceof DoStatement) {
				DoStatement oldDoStatement = (DoStatement)oldPredicateStatement;
				DoStatement newDoStatement = ast.newDoStatement();
				newPredicateStatement = newDoStatement;
				sourceRewriter.set(newDoStatement, DoStatement.EXPRESSION_PROPERTY, oldDoStatement.getExpression(), null);
				sourceRewriter.set(newDoStatement, DoStatement.BODY_PROPERTY, loopBlock, null);
			}
		}
		LabeledStatement labeled = belongsToLabeledStatement(predicateNode);
		if(labeled != null) {
			labeledStatementsToBeRemoved.add(labeled);
			LabeledStatement newLabeledStatement = ast.newLabeledStatement();
			sourceRewriter.set(newLabeledStatement, LabeledStatement.LABEL_PROPERTY, labeled.getLabel(), null);
			sourceRewriter.set(newLabeledStatement, LabeledStatement.BODY_PROPERTY, newPredicateStatement, null);
			newPredicateStatement = newLabeledStatement;
		}
		return newPredicateStatement;
	}

	protected void processStatementNode(ListRewrite bodyRewrite, PDGNode dstPDGNode, AST ast, ASTRewrite sourceRewriter) {
		bodyRewrite.insertLast(dstPDGNode.getASTStatement(), null);
	}

	private LabeledStatement belongsToLabeledStatement(PDGNode pdgNode) {
		Statement statement = pdgNode.getASTStatement();
		if(statement.getParent() instanceof LabeledStatement) {
			return (LabeledStatement) statement.getParent();
		}
		return null;
	}

	protected ListRewrite createTryStatementIfNeeded(ASTRewrite sourceRewriter, AST ast, ListRewrite bodyRewrite, PDGNode node) {
		Statement statement = node.getASTStatement();
		ASTNode statementParent = statement.getParent();
		if(statementParent != null && statementParent instanceof Block)
			statementParent = statementParent.getParent();
		if(statementParent != null && statementParent instanceof TryStatement) {
			TryStatement tryStatementParent = (TryStatement)statementParent;
			if(tryStatementsToBeRemoved.contains(tryStatementParent) || tryStatementsToBeCopied.contains(tryStatementParent)) {
				if(tryStatementBodyRewriteMap.containsKey(tryStatementParent)) {
					bodyRewrite = tryStatementBodyRewriteMap.get(tryStatementParent);
				}
				else {
					TryStatement newTryStatement = copyTryStatement(sourceRewriter, ast, tryStatementParent);
					Block tryMethodBody = ast.newBlock();
					sourceRewriter.set(newTryStatement, TryStatement.BODY_PROPERTY, tryMethodBody, null);
					ListRewrite tryBodyRewrite = sourceRewriter.getListRewrite(tryMethodBody, Block.STATEMENTS_PROPERTY);
					tryStatementBodyRewriteMap.put(tryStatementParent, tryBodyRewrite);
					bodyRewrite.insertLast(newTryStatement, null);
					bodyRewrite = tryBodyRewrite;
				}
			}
		}
		return bodyRewrite;
	}

	protected TryStatement copyTryStatement(ASTRewrite sourceRewriter, AST ast, TryStatement tryStatementParent) {
		TryStatement newTryStatement = ast.newTryStatement();
		ListRewrite resourceRewrite = sourceRewriter.getListRewrite(newTryStatement, TryStatement.RESOURCES_PROPERTY);
		List<VariableDeclarationExpression> resources = tryStatementParent.resources();
		for(VariableDeclarationExpression expression : resources) {
			resourceRewrite.insertLast(expression, null);
		}
		ListRewrite catchClauseRewrite = sourceRewriter.getListRewrite(newTryStatement, TryStatement.CATCH_CLAUSES_PROPERTY);
		List<CatchClause> catchClauses = tryStatementParent.catchClauses();
		for(CatchClause catchClause : catchClauses) {
			catchClauseRewrite.insertLast(catchClause, null);
		}
		if(tryStatementParent.getFinally() != null) {
			sourceRewriter.set(newTryStatement, TryStatement.FINALLY_PROPERTY, tryStatementParent.getFinally(), null);
		}
		return newTryStatement;
	}

	protected PDGControlPredicateNode isInsideDoLoop(PDGNode node) {
		for(CFGBranchDoLoopNode doLoopNode : doLoopNodes) {
			if(node.getId() >= doLoopNode.getJoinNode().getId() && node.getId() < doLoopNode.getId()) {
				PDGControlPredicateNode predicateNode = (PDGControlPredicateNode)doLoopNode.getPDGNode();
				return predicateNode;
			}
		}
		return null;
	}

	protected TryStatement surroundingTryBlock(ASTNode statement) {
		ASTNode parent = statement.getParent();
		while(!(parent instanceof MethodDeclaration)) {
			if(parent instanceof TryStatement)
				return (TryStatement)parent;
			parent = parent.getParent();
		}
		return null;
	}

	protected boolean tryBlockCatchesExceptionType(TryStatement tryStatement, ITypeBinding exceptionType) {
		List<CatchClause> catchClauses = tryStatement.catchClauses();
		for(CatchClause catchClause : catchClauses) {
			SingleVariableDeclaration exceptionDeclaration = catchClause.getException();
			Type exceptionDeclarationType = exceptionDeclaration.getType();
			if(exceptionDeclarationType instanceof UnionType) {
				UnionType unionType = (UnionType)exceptionDeclarationType;
				List<Type> types = unionType.types();
				for(Type type : types) {
					ITypeBinding exceptionDeclarationTypeBinding = type.resolveBinding();
					if(equalOrExtend(exceptionType, exceptionDeclarationTypeBinding))
						return true;
				}
			}
			else {
				ITypeBinding exceptionDeclarationTypeBinding = exceptionDeclarationType.resolveBinding();
				if(equalOrExtend(exceptionType, exceptionDeclarationTypeBinding))
					return true;
			}
		}
		return false;
	}

	private boolean equalOrExtend(ITypeBinding subTypeBinding, ITypeBinding superTypeBinding) {
		if(subTypeBinding.isEqualTo(superTypeBinding))
			return true;
		ITypeBinding superClassTypeBinding = subTypeBinding.getSuperclass();
		if(superClassTypeBinding != null) {
			return equalOrExtend(superClassTypeBinding, superTypeBinding);
		}
		else {
			return false;
		}
	}

	protected ITypeBinding extractTypeBinding(VariableDeclaration variableDeclaration) {
		IVariableBinding variableBinding = variableDeclaration.resolveBinding();
		return variableBinding.getType();
	}

	protected Expression generateDefaultValue(ASTRewrite sourceRewriter, AST ast, ITypeBinding returnTypeBinding) {
		Expression returnedExpression = null;
		if(returnTypeBinding.isPrimitive()) {
			if(returnTypeBinding.getQualifiedName().equals("boolean")) {
				returnedExpression = ast.newBooleanLiteral(false);
			}
			else if(returnTypeBinding.getQualifiedName().equals("char")) {
				CharacterLiteral characterLiteral = ast.newCharacterLiteral();
				sourceRewriter.set(characterLiteral, CharacterLiteral.ESCAPED_VALUE_PROPERTY, "\u0000", null);
				returnedExpression = characterLiteral;
			}
			else if(returnTypeBinding.getQualifiedName().equals("int") ||
					returnTypeBinding.getQualifiedName().equals("short") ||
					returnTypeBinding.getQualifiedName().equals("byte")) {
				returnedExpression = ast.newNumberLiteral("0");
			}
			else if(returnTypeBinding.getQualifiedName().equals("long")) {
				returnedExpression = ast.newNumberLiteral("0L");
			}
			else if(returnTypeBinding.getQualifiedName().equals("float")) {
				returnedExpression = ast.newNumberLiteral("0.0f");
			}
			else if(returnTypeBinding.getQualifiedName().equals("double")) {
				returnedExpression = ast.newNumberLiteral("0.0d");
			}
			else if(returnTypeBinding.getQualifiedName().equals("void")) {
				returnedExpression = null;
			}
		}
		else {
			returnedExpression = ast.newNullLiteral();
		}
		return returnedExpression;
	}
}
