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

import gr.uom.java.ast.decomposition.CompositeStatementObject;
import gr.uom.java.ast.inheritance.InheritanceTree;
import gr.uom.java.ast.util.ExpressionExtractor;
import gr.uom.java.ast.util.MethodDeclarationUtility;
import gr.uom.java.ast.util.StatementExtractor;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import javax.swing.tree.DefaultMutableTreeNode;

import org.eclipse.core.resources.IFile;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BreakStatement;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.Expression;
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.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.SwitchCase;
import org.eclipse.jdt.core.dom.SwitchStatement;
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.UnionType;
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;

public class TypeCheckElimination implements Comparable<TypeCheckElimination> {
	private Map<Expression, ArrayList<Statement>> typeCheckMap;
	private ArrayList<Statement> defaultCaseStatements;
	private Map<Expression, List<SimpleName>> staticFieldMap;
	private Map<Expression, List<Type>> subclassTypeMap;
	private VariableDeclarationFragment typeField;
	private MethodDeclaration typeFieldGetterMethod;
	private MethodDeclaration typeFieldSetterMethod;
	private Statement typeCheckCodeFragment;
	private CompositeStatementObject typeCheckCompositeStatement;
	private MethodDeclaration typeCheckMethod;
	private TypeDeclaration typeCheckClass;
	private IFile typeCheckIFile;
	private LinkedHashSet<SimpleName> additionalStaticFields;
	private LinkedHashSet<VariableDeclarationFragment> accessedFields;
	private LinkedHashSet<VariableDeclarationFragment> assignedFields;
	private LinkedHashMap<VariableDeclarationFragment, MethodDeclaration> superAccessedFieldMap;
	private LinkedHashMap<IVariableBinding, IMethodBinding> superAccessedFieldBindingMap;
	private LinkedHashMap<VariableDeclarationFragment, MethodDeclaration> superAssignedFieldMap;
	private LinkedHashMap<IVariableBinding, IMethodBinding> superAssignedFieldBindingMap;
	private LinkedHashSet<SingleVariableDeclaration> accessedParameters;
	private LinkedHashSet<SingleVariableDeclaration> assignedParameters;
	private LinkedHashSet<VariableDeclaration> accessedLocalVariables;
	private LinkedHashSet<VariableDeclaration> assignedLocalVariables;
	private LinkedHashSet<MethodDeclaration> accessedMethods;
	private LinkedHashSet<IMethodBinding> superAccessedMethods;
	private VariableDeclaration typeLocalVariable;
	private MethodInvocation typeMethodInvocation;
	private VariableDeclarationFragment foreignTypeField;
	private InheritanceTree existingInheritanceTree;
	private InheritanceTree inheritanceTreeMatchingWithStaticTypes;
	private Map<SimpleName, String> staticFieldSubclassTypeMap;
	private Map<Expression, DefaultMutableTreeNode> remainingIfStatementExpressionMap;
	private String abstractMethodName;
	private volatile int hashCode = 0;
	private int groupSizeAtClassLevel;
	private double averageNumberOfStatements;
	private Integer userRate;
	
	public TypeCheckElimination() {
		this.typeCheckMap = new LinkedHashMap<Expression, ArrayList<Statement>>();
		this.defaultCaseStatements = new ArrayList<Statement>();
		this.staticFieldMap = new LinkedHashMap<Expression, List<SimpleName>>();
		this.subclassTypeMap = new LinkedHashMap<Expression, List<Type>>();
		this.typeField = null;
		this.typeFieldGetterMethod = null;
		this.typeFieldSetterMethod = null;
		this.typeCheckCodeFragment = null;
		this.typeCheckMethod = null;
		this.typeCheckClass = null;
		this.additionalStaticFields = new LinkedHashSet<SimpleName>();
		this.accessedFields = new LinkedHashSet<VariableDeclarationFragment>();
		this.assignedFields = new LinkedHashSet<VariableDeclarationFragment>();
		this.superAccessedFieldMap = new LinkedHashMap<VariableDeclarationFragment, MethodDeclaration>();
		this.superAccessedFieldBindingMap = new LinkedHashMap<IVariableBinding, IMethodBinding>();
		this.superAssignedFieldMap = new LinkedHashMap<VariableDeclarationFragment, MethodDeclaration>();
		this.superAssignedFieldBindingMap = new LinkedHashMap<IVariableBinding, IMethodBinding>();
		this.accessedParameters = new LinkedHashSet<SingleVariableDeclaration>();
		this.assignedParameters = new LinkedHashSet<SingleVariableDeclaration>();
		this.accessedLocalVariables = new LinkedHashSet<VariableDeclaration>();
		this.assignedLocalVariables = new LinkedHashSet<VariableDeclaration>();
		this.accessedMethods = new LinkedHashSet<MethodDeclaration>();
		this.superAccessedMethods = new LinkedHashSet<IMethodBinding>();
		this.typeLocalVariable = null;
		this.typeMethodInvocation = null;
		this.foreignTypeField = null;
		this.existingInheritanceTree = null;
		this.inheritanceTreeMatchingWithStaticTypes = null;
		this.staticFieldSubclassTypeMap = new LinkedHashMap<SimpleName, String>();
		this.remainingIfStatementExpressionMap = new LinkedHashMap<Expression, DefaultMutableTreeNode>();
		this.abstractMethodName = null;
	}
	
	public void addTypeCheck(Expression expression, Statement statement) {
		if(typeCheckMap.containsKey(expression)) {
			ArrayList<Statement> statements = typeCheckMap.get(expression);
			statements.add(statement);
		}
		else {
			ArrayList<Statement> statements = new ArrayList<Statement>();
			statements.add(statement);
			typeCheckMap.put(expression, statements);
		}
	}
	
	public void addEmptyTypeCheck(Expression expression) {
		if(!typeCheckMap.containsKey(expression)) {
			ArrayList<Statement> statements = new ArrayList<Statement>();
			typeCheckMap.put(expression, statements);
		}
	}
	
	public boolean containsTypeCheckExpression(Expression expression) {
		return typeCheckMap.containsKey(expression);
	}
	
	public void addDefaultCaseStatement(Statement statement) {
		defaultCaseStatements.add(statement);
	}
	
	public void addStaticType(Expression expression, List<SimpleName> simpleNameGroup) {
		staticFieldMap.put(expression, simpleNameGroup);
	}
	
	public void addSubclassType(Expression expression, List<Type> subclassTypeGroup) {
		subclassTypeMap.put(expression, subclassTypeGroup);
	}
	
	public void addRemainingIfStatementExpression(Expression expression, DefaultMutableTreeNode root) {
		remainingIfStatementExpressionMap.put(expression, root);
	}
	
	public void addAdditionalStaticField(SimpleName staticField) {
		additionalStaticFields.add(staticField);
	}
	
	public void addAccessedField(VariableDeclarationFragment fragment) {
		accessedFields.add(fragment);
	}
	
	public void addAssignedField(VariableDeclarationFragment fragment) {
		assignedFields.add(fragment);
	}
	
	public void addSuperAccessedField(VariableDeclarationFragment fragment, MethodDeclaration method) {
		superAccessedFieldMap.put(fragment, method);
	}
	
	public void addSuperAccessedFieldBinding(IVariableBinding variableBinding, IMethodBinding methodBinding) {
		superAccessedFieldBindingMap.put(variableBinding, methodBinding);
	}
	
	public IMethodBinding getGetterMethodBindingOfSuperAccessedField(IVariableBinding variableBinding) {
		return superAccessedFieldBindingMap.get(variableBinding);
	}

	public Set<VariableDeclarationFragment> getSuperAccessedFields() {
		return superAccessedFieldMap.keySet();
	}
	
	public Set<IVariableBinding> getSuperAccessedFieldBindings() {
		return superAccessedFieldBindingMap.keySet();
	}
	
	public void addSuperAssignedField(VariableDeclarationFragment fragment, MethodDeclaration method) {
		superAssignedFieldMap.put(fragment, method);
	}
	
	public void addSuperAssignedFieldBinding(IVariableBinding variableBinding, IMethodBinding methodBinding) {
		superAssignedFieldBindingMap.put(variableBinding, methodBinding);
	}

	public IMethodBinding getSetterMethodBindingOfSuperAssignedField(IVariableBinding variableBinding) {
		return superAssignedFieldBindingMap.get(variableBinding);
	}

	public Set<VariableDeclarationFragment> getSuperAssignedFields() {
		return superAssignedFieldMap.keySet();
	}
	
	public Set<IVariableBinding> getSuperAssignedFieldBindings() {
		return superAssignedFieldBindingMap.keySet();
	}
	
	public void addAccessedLocalVariable(VariableDeclaration fragment) {
		accessedLocalVariables.add(fragment);
	}

	public void addAssignedLocalVariable(VariableDeclaration fragment) {
		assignedLocalVariables.add(fragment);
	}
	
	public void addAccessedParameter(SingleVariableDeclaration parameter) {
		accessedParameters.add(parameter);
	}
	
	public void addAssignedParameter(SingleVariableDeclaration parameter) {
		assignedParameters.add(parameter);
	}
	
	public void addAccessedMethod(MethodDeclaration method) {
		accessedMethods.add(method);
	}

	public void addSuperAccessedMethod(IMethodBinding method) {
		superAccessedMethods.add(method);
	}
	
	public LinkedHashSet<VariableDeclaration> getAccessedLocalVariables() {
		return accessedLocalVariables;
	}

	public Set<VariableDeclarationFragment> getAccessedFields() {
		return accessedFields;
	}
	
	public Set<VariableDeclarationFragment> getAssignedFields() {
		return assignedFields;
	}
	
	public Set<SingleVariableDeclaration> getAccessedParameters() {
		return accessedParameters;
	}
	
	public Set<MethodDeclaration> getAccessedMethods() {
		return accessedMethods;
	}

	public Set<IMethodBinding> getSuperAccessedMethods() {
		return superAccessedMethods;
	}
	
	public Set<Expression> getTypeCheckExpressions() {
		return typeCheckMap.keySet();
	}
	
	public ArrayList<Statement> getTypeCheckStatements(Expression expression) {
		return typeCheckMap.get(expression);
	}
	
	public List<ArrayList<Statement>> getTypeCheckStatements() {
		return new ArrayList<ArrayList<Statement>>(typeCheckMap.values());
	}
	
	public ArrayList<Statement> getDefaultCaseStatements() {
		return defaultCaseStatements;
	}
	
	public List<SimpleName> getStaticFields(Expression expression) {
		return staticFieldMap.get(expression);
	}
	
	public List<SimpleName> getStaticFields() {
		ArrayList<SimpleName> staticFields = new ArrayList<SimpleName>();
		for(Expression expression : typeCheckMap.keySet()) {
			List<SimpleName> simpleNameGroup = staticFieldMap.get(expression);
			if(simpleNameGroup != null) {
				for(SimpleName simpleName : simpleNameGroup)
					staticFields.add(simpleName);
			}
		}
		return staticFields;
	}
	
	public Set<SimpleName> getAdditionalStaticFields() {
		return additionalStaticFields;
	}
	
	public DefaultMutableTreeNode getRemainingIfStatementExpression(Expression expression) {
		return remainingIfStatementExpressionMap.get(expression);
	}
	
	public Expression getExpressionCorrespondingToTypeCheckStatementList(ArrayList<Statement> statements) {
		for(Expression expression : typeCheckMap.keySet()) {
			if(statements.equals(typeCheckMap.get(expression)))
				return expression;
		}
		return null;
	}
	
	public VariableDeclarationFragment getTypeField() {
		return typeField;
	}
	
	public void setTypeField(VariableDeclarationFragment typeField) {
		this.typeField = typeField;
	}
	
	public MethodDeclaration getTypeFieldGetterMethod() {
		return typeFieldGetterMethod;
	}

	public void setTypeFieldGetterMethod(MethodDeclaration typeFieldGetterMethod) {
		this.typeFieldGetterMethod = typeFieldGetterMethod;
	}

	public MethodDeclaration getTypeFieldSetterMethod() {
		return typeFieldSetterMethod;
	}

	public void setTypeFieldSetterMethod(MethodDeclaration typeFieldSetterMethod) {
		this.typeFieldSetterMethod = typeFieldSetterMethod;
	}

	public Statement getTypeCheckCodeFragment() {
		return typeCheckCodeFragment;
	}

	public void setTypeCheckCodeFragment(Statement typeCheckCodeFragment) {
		this.typeCheckCodeFragment = typeCheckCodeFragment;
	}

	public CompositeStatementObject getTypeCheckCompositeStatement() {
		return typeCheckCompositeStatement;
	}

	public void setTypeCheckCompositeStatement(CompositeStatementObject typeCheckCompositeStatement) {
		this.typeCheckCompositeStatement = typeCheckCompositeStatement;
	}

	public MethodDeclaration getTypeCheckMethod() {
		return typeCheckMethod;
	}

	public void setTypeCheckMethod(MethodDeclaration typeCheckMethod) {
		this.typeCheckMethod = typeCheckMethod;
		this.abstractMethodName = typeCheckMethod.getName().getIdentifier();
	}

	public TypeDeclaration getTypeCheckClass() {
		return typeCheckClass;
	}

	public void setTypeCheckClass(TypeDeclaration typeCheckClass) {
		this.typeCheckClass = typeCheckClass;
	}

	public IFile getTypeCheckIFile() {
		return typeCheckIFile;
	}

	public void setTypeCheckIFile(IFile typeCheckIFile) {
		this.typeCheckIFile = typeCheckIFile;
	}

	public VariableDeclaration getTypeLocalVariable() {
		return typeLocalVariable;
	}

	public void setTypeLocalVariable(VariableDeclaration typeLocalVariable) {
		this.typeLocalVariable = typeLocalVariable;
	}

	public MethodInvocation getTypeMethodInvocation() {
		return typeMethodInvocation;
	}

	public void setTypeMethodInvocation(MethodInvocation typeMethodInvocation) {
		this.typeMethodInvocation = typeMethodInvocation;
	}

	public VariableDeclarationFragment getForeignTypeField() {
		return foreignTypeField;
	}

	public void setForeignTypeField(VariableDeclarationFragment foreignTypeField) {
		this.foreignTypeField = foreignTypeField;
	}

	public InheritanceTree getExistingInheritanceTree() {
		return existingInheritanceTree;
	}

	public void setExistingInheritanceTree(InheritanceTree existingInheritanceTree) {
		this.existingInheritanceTree = existingInheritanceTree;
	}

	public InheritanceTree getInheritanceTreeMatchingWithStaticTypes() {
		return inheritanceTreeMatchingWithStaticTypes;
	}

	public void setInheritanceTreeMatchingWithStaticTypes(InheritanceTree inheritanceTree) {
		this.inheritanceTreeMatchingWithStaticTypes = inheritanceTree;
	}

	public void putStaticFieldSubclassTypeMapping(SimpleName staticField, String subclassType) {
		staticFieldSubclassTypeMap.put(staticField, subclassType);
	}

	public boolean allTypeCheckingsContainStaticFieldOrSubclassType() {
		return (typeCheckMap.keySet().size() > 1 || (typeCheckMap.keySet().size() == 1 && !defaultCaseStatements.isEmpty())) && 
			(typeCheckMap.keySet().size() == (staticFieldMap.keySet().size() + subclassTypeMap.keySet().size()));
	}
	
	public boolean isApplicable() {
		if(!containsLocalVariableAssignment() && !containsBranchingStatement() && !containsSuperMethodInvocation() && !containsSuperFieldAccess() &&
				!isSubclassTypeAnInterface() && !returnStatementAfterTypeCheckCodeFragment())
			return true;
		else
			return false;
	}
	
	private boolean isSubclassTypeAnInterface() {
		for(List<Type> subTypes : subclassTypeMap.values()) {
			for(Type subType : subTypes) {
				if(subType.resolveBinding().isInterface())
					return true;
			}
		}
		return false;
	}
	
	private boolean returnStatementAfterTypeCheckCodeFragment() {
		//check if the type-check code fragment contains return statements having an expression
		StatementExtractor statementExtractor = new StatementExtractor();
		List<Statement> allReturnStatementsWithinTypeCheckCodeFragment = statementExtractor.getReturnStatements(typeCheckCodeFragment);
		List<ReturnStatement> returnStatementsHavingExpressionWithinTypeCheckCodeFragment = new ArrayList<ReturnStatement>();
		for(Statement statement : allReturnStatementsWithinTypeCheckCodeFragment) {
			ReturnStatement returnStatement = (ReturnStatement)statement;
			if(returnStatement.getExpression() != null)
				returnStatementsHavingExpressionWithinTypeCheckCodeFragment.add(returnStatement);
		}
		if(returnStatementsHavingExpressionWithinTypeCheckCodeFragment.isEmpty())
			return false;
		//get all return statements having an expression within method body
		List<Statement> allReturnStatementsWithinTypeCheckMethod = statementExtractor.getReturnStatements(typeCheckMethod.getBody());
		List<ReturnStatement> returnStatementsHavingExpressionWithinTypeCheckMethod = new ArrayList<ReturnStatement>();
		for(Statement statement : allReturnStatementsWithinTypeCheckMethod) {
			ReturnStatement returnStatement = (ReturnStatement)statement;
			if(returnStatement.getExpression() != null)
				returnStatementsHavingExpressionWithinTypeCheckMethod.add(returnStatement);
		}
		List<ReturnStatement> returnStatementsHavingExpressionOutsideTypeCheckMethod = new ArrayList<ReturnStatement>();
		returnStatementsHavingExpressionOutsideTypeCheckMethod.addAll(returnStatementsHavingExpressionWithinTypeCheckMethod);
		returnStatementsHavingExpressionOutsideTypeCheckMethod.removeAll(returnStatementsHavingExpressionWithinTypeCheckCodeFragment);
		for(ReturnStatement returnStatement : returnStatementsHavingExpressionOutsideTypeCheckMethod) {
			if(returnStatement.getStartPosition() > typeCheckCodeFragment.getStartPosition()+typeCheckCodeFragment.getLength())
				return true;
		}
		return false;
	}

	private boolean containsLocalVariableAssignment() {
		VariableDeclaration returnedVariableDeclaration = getTypeCheckMethodReturnedVariable();
		ExpressionExtractor expressionExtractor = new ExpressionExtractor();
		StatementExtractor statementExtractor = new StatementExtractor();
		List<ArrayList<Statement>> allTypeCheckStatements = getTypeCheckStatements();
		if(!getDefaultCaseStatements().isEmpty()) {
			allTypeCheckStatements.add(getDefaultCaseStatements());
		}
		for(ArrayList<Statement> typeCheckStatementList : allTypeCheckStatements) {
			List<VariableDeclarationFragment> variableDeclarationFragmentsInsideBranch = new ArrayList<VariableDeclarationFragment>();
			for(Statement statement : typeCheckStatementList) {
				List<Statement> variableDeclarationStatements = statementExtractor.getVariableDeclarationStatements(statement);
				for(Statement statement2 : variableDeclarationStatements) {
					VariableDeclarationStatement variableDeclarationStatement = (VariableDeclarationStatement)statement2;
					List<VariableDeclarationFragment> fragments = variableDeclarationStatement.fragments();
					variableDeclarationFragmentsInsideBranch.addAll(fragments);
				}
				List<Expression> variableDeclarationExpressions = expressionExtractor.getVariableDeclarationExpressions(statement);
				for(Expression expression : variableDeclarationExpressions) {
					VariableDeclarationExpression variableDeclarationExpression = (VariableDeclarationExpression)expression;
					List<VariableDeclarationFragment> fragments = variableDeclarationExpression.fragments();
					variableDeclarationFragmentsInsideBranch.addAll(fragments);
				}
			}
			for(Statement statement : typeCheckStatementList) {
				List<Expression> assignments = expressionExtractor.getAssignments(statement);
				for(Expression expression : assignments) {
					Assignment assignment = (Assignment)expression;
					Expression leftHandSide = assignment.getLeftHandSide();
					SimpleName leftHandSideName = null;
					if(leftHandSide instanceof SimpleName) {
						leftHandSideName = (SimpleName)leftHandSide;
					}
					else if(leftHandSide instanceof QualifiedName) {
						QualifiedName leftHandSideQualifiedName = (QualifiedName)leftHandSide;
						leftHandSideName = leftHandSideQualifiedName.getName();
					}
					else if(leftHandSide instanceof FieldAccess) {
						FieldAccess leftHandSideFieldAccess = (FieldAccess)leftHandSide;
						leftHandSideName = leftHandSideFieldAccess.getName();
					}
					if(leftHandSideName != null) {
						IBinding leftHandSideBinding = leftHandSideName.resolveBinding();
						if(leftHandSideBinding != null && leftHandSideBinding.getKind() == IBinding.VARIABLE) {
							IVariableBinding leftHandSideVariableBinding = (IVariableBinding)leftHandSideBinding;
							if(!leftHandSideVariableBinding.isField()) {
								boolean variableIsDeclaredInsideBranch = false;
								for(VariableDeclarationFragment fragment : variableDeclarationFragmentsInsideBranch) {
									IVariableBinding fragmentVariableBinding = fragment.resolveBinding();
									if(fragmentVariableBinding.isEqualTo(leftHandSideVariableBinding)) {
										variableIsDeclaredInsideBranch = true;
										break;
									}
								}
								if(!variableIsDeclaredInsideBranch) {
									if(returnedVariableDeclaration == null) {
										return true;
									}
									else if(!returnedVariableDeclaration.resolveBinding().isEqualTo(leftHandSideVariableBinding)) {
										return true;
									}
								}
							}
						}
					}
				}
				List<Expression> postfixExpressions = expressionExtractor.getPostfixExpressions(statement);
				for(Expression expression : postfixExpressions) {
					PostfixExpression postfix = (PostfixExpression)expression;
					Expression operand = postfix.getOperand();
					SimpleName operandName = null;
					if(operand instanceof SimpleName) {
						operandName = (SimpleName)operand;
					}
					else if(operand instanceof QualifiedName) {
						QualifiedName qualifiedName = (QualifiedName)operand;
						operandName = qualifiedName.getName();
					}
					else if(operand instanceof FieldAccess) {
						FieldAccess fieldAccess = (FieldAccess)operand;
						operandName = fieldAccess.getName();
					}
					if(operandName != null) {
						IBinding operandBinding = operandName.resolveBinding();
						if(operandBinding != null && operandBinding.getKind() == IBinding.VARIABLE) {
							IVariableBinding operandVariableBinding = (IVariableBinding)operandBinding;
							if(!operandVariableBinding.isField()) {
								boolean variableIsDeclaredInsideBranch = false;
								for(VariableDeclarationFragment fragment : variableDeclarationFragmentsInsideBranch) {
									IVariableBinding fragmentVariableBinding = fragment.resolveBinding();
									if(fragmentVariableBinding.isEqualTo(operandVariableBinding)) {
										variableIsDeclaredInsideBranch = true;
										break;
									}
								}
								if(!variableIsDeclaredInsideBranch) {
									if(returnedVariableDeclaration == null) {
										return true;
									}
									else if(!returnedVariableDeclaration.resolveBinding().isEqualTo(operandVariableBinding)) {
										return true;
									}
								}
							}
						}
					}
				}
				List<Expression> prefixExpressions = expressionExtractor.getPrefixExpressions(statement);
				for(Expression expression : prefixExpressions) {
					PrefixExpression prefix = (PrefixExpression)expression;
					if(prefix.getOperator().equals(PrefixExpression.Operator.INCREMENT) ||
							prefix.getOperator().equals(PrefixExpression.Operator.DECREMENT)) {
						Expression operand = prefix.getOperand();
						SimpleName operandName = null;
						if(operand instanceof SimpleName) {
							operandName = (SimpleName)operand;
						}
						else if(operand instanceof QualifiedName) {
							QualifiedName qualifiedName = (QualifiedName)operand;
							operandName = qualifiedName.getName();
						}
						else if(operand instanceof FieldAccess) {
							FieldAccess fieldAccess = (FieldAccess)operand;
							operandName = fieldAccess.getName();
						}
						if(operandName != null) {
							IBinding operandBinding = operandName.resolveBinding();
							if(operandBinding != null && operandBinding.getKind() == IBinding.VARIABLE) {
								IVariableBinding operandVariableBinding = (IVariableBinding)operandBinding;
								if(!operandVariableBinding.isField()) {
									boolean variableIsDeclaredInsideBranch = false;
									for(VariableDeclarationFragment fragment : variableDeclarationFragmentsInsideBranch) {
										IVariableBinding fragmentVariableBinding = fragment.resolveBinding();
										if(fragmentVariableBinding.isEqualTo(operandVariableBinding)) {
											variableIsDeclaredInsideBranch = true;
											break;
										}
									}
									if(!variableIsDeclaredInsideBranch) {
										if(returnedVariableDeclaration == null) {
											return true;
										}
										else if(!returnedVariableDeclaration.resolveBinding().isEqualTo(operandVariableBinding)) {
											return true;
										}
									}
								}
							}
						}
					}
				}
			}
		}
		return false;
	}
	
	private boolean containsBranchingStatement() {
		List<Statement> statementList = new ArrayList<Statement>();
		StatementExtractor statementExtractor = new StatementExtractor();
		List<ArrayList<Statement>> typeCheckStatements = getTypeCheckStatements();
		if(!defaultCaseStatements.isEmpty())
			typeCheckStatements.add(defaultCaseStatements);
		for(ArrayList<Statement> statements : typeCheckStatements) {
			for(Statement statement : statements) {
				statementList.addAll(statementExtractor.getBreakStatements(statement));
				statementList.addAll(statementExtractor.getContinueStatements(statement));
				List<Statement> returnStatements = statementExtractor.getReturnStatements(statement);
				for(Statement statement2 : returnStatements) {
					ReturnStatement returnStatement = (ReturnStatement)statement2;
					if(returnStatement.getExpression() == null)
						statementList.add(returnStatement);
				}
				
				List<Statement> forStatements = statementExtractor.getForStatements(statement);
				for(Statement forStatement : forStatements) {
					statementList.removeAll(statementExtractor.getBreakStatements(forStatement));
		    		statementList.removeAll(statementExtractor.getContinueStatements(forStatement));
		    		statementList.removeAll(statementExtractor.getReturnStatements(forStatement));
				}
				List<Statement> whileStatements = statementExtractor.getWhileStatements(statement);
				for(Statement whileStatement : whileStatements) {
					statementList.removeAll(statementExtractor.getBreakStatements(whileStatement));
		    		statementList.removeAll(statementExtractor.getContinueStatements(whileStatement));
		    		statementList.removeAll(statementExtractor.getReturnStatements(whileStatement));
				}
				List<Statement> doStatements = statementExtractor.getDoStatements(statement);
				for(Statement doStatement : doStatements) {
					statementList.removeAll(statementExtractor.getBreakStatements(doStatement));
		    		statementList.removeAll(statementExtractor.getContinueStatements(doStatement));
		    		statementList.removeAll(statementExtractor.getReturnStatements(doStatement));
				}
				List<Statement> enchancedForStatements = statementExtractor.getEnhancedForStatements(statement);
				for(Statement enchancedForStatement : enchancedForStatements) {
					statementList.removeAll(statementExtractor.getBreakStatements(enchancedForStatement));
		    		statementList.removeAll(statementExtractor.getContinueStatements(enchancedForStatement));
		    		statementList.removeAll(statementExtractor.getReturnStatements(enchancedForStatement));
				}
				List<Statement> switchStatements = statementExtractor.getSwitchStatements(statement);
				for(Statement switchStatement : switchStatements) {
					statementList.removeAll(statementExtractor.getBreakStatements(switchStatement));
		    		statementList.removeAll(statementExtractor.getContinueStatements(switchStatement));
		    		statementList.removeAll(statementExtractor.getReturnStatements(switchStatement));
				}
			}
		}
		if(!statementList.isEmpty())
			return true;
		else
			return false;
	}
	
	private boolean containsSuperMethodInvocation() {
		List<ArrayList<Statement>> typeCheckStatements = getTypeCheckStatements();
		if(!defaultCaseStatements.isEmpty())
			typeCheckStatements.add(defaultCaseStatements);
		
		ExpressionExtractor expressionExtractor = new ExpressionExtractor();
		for(ArrayList<Statement> statements : typeCheckStatements) {
			for(Statement statement : statements) {
				List<Expression> superMethodInvocations = expressionExtractor.getSuperMethodInvocations(statement);
				if(!superMethodInvocations.isEmpty())
					return true;
			}
		}
		return false;
	}

	private boolean containsSuperFieldAccess() {
		List<ArrayList<Statement>> typeCheckStatements = getTypeCheckStatements();
		if(!defaultCaseStatements.isEmpty())
			typeCheckStatements.add(defaultCaseStatements);
		
		ExpressionExtractor expressionExtractor = new ExpressionExtractor();
		for(ArrayList<Statement> statements : typeCheckStatements) {
			for(Statement statement : statements) {
				List<Expression> superFieldAccesses = expressionExtractor.getSuperFieldAccesses(statement);
				if(!superFieldAccesses.isEmpty())
					return true;
			}
		}
		return false;
	}
	
	public Type getTypeCheckMethodReturnType() {
		return typeCheckMethod.getReturnType2();
	}
	
	public List<SingleVariableDeclaration> getTypeCheckMethodParameters() {
		return typeCheckMethod.parameters();
	}
	
	public VariableDeclaration getTypeCheckMethodReturnedVariable() {
		StatementExtractor statementExtractor = new StatementExtractor();
		ExpressionExtractor expressionExtractor = new ExpressionExtractor();
		List<Statement> typeCheckCodeFragmentReturnStatements = statementExtractor.getReturnStatements(typeCheckCodeFragment);
		if(!typeCheckCodeFragmentReturnStatements.isEmpty()) {
			ReturnStatement firstReturnStatement = (ReturnStatement)typeCheckCodeFragmentReturnStatements.get(0);
			if(firstReturnStatement.getExpression() instanceof SimpleName) {
				SimpleName returnExpression = (SimpleName)firstReturnStatement.getExpression();
				List<SingleVariableDeclaration> parameters = typeCheckMethod.parameters();
				for(SingleVariableDeclaration parameter : parameters) {
					if(parameter.resolveBinding().isEqualTo(returnExpression.resolveBinding()))
						return parameter;
				}
				List<Statement> variableDeclarationStatements = statementExtractor.getVariableDeclarationStatements(typeCheckMethod.getBody());
				for(Statement statement : variableDeclarationStatements) {
					VariableDeclarationStatement variableDeclarationStatement = (VariableDeclarationStatement)statement;
					List<VariableDeclarationFragment> fragments = variableDeclarationStatement.fragments();
					for(VariableDeclarationFragment fragment : fragments) {
						if(fragment.resolveBinding().isEqualTo(returnExpression.resolveBinding()))
							return fragment;
					}
				}
				List<Expression> variableDeclarationExpressions = expressionExtractor.getVariableDeclarationExpressions(typeCheckMethod.getBody());
				for(Expression expression : variableDeclarationExpressions) {
					VariableDeclarationExpression variableDeclarationExpression = (VariableDeclarationExpression)expression;
					List<VariableDeclarationFragment> fragments = variableDeclarationExpression.fragments();
					for(VariableDeclarationFragment fragment : fragments) {
						if(fragment.resolveBinding().isEqualTo(returnExpression.resolveBinding()))
							return fragment;
					}
				}
			}
		}
		else {
			List<Statement> allReturnStatements = statementExtractor.getReturnStatements(typeCheckMethod.getBody());
			if(!allReturnStatements.isEmpty()) {
				ReturnStatement lastReturnStatement = (ReturnStatement)allReturnStatements.get(allReturnStatements.size()-1);
				if(lastReturnStatement.getExpression() instanceof SimpleName) {
					SimpleName returnExpression = (SimpleName)lastReturnStatement.getExpression();
					for(SingleVariableDeclaration assignedParameter : assignedParameters) {
						if(assignedParameter.resolveBinding().isEqualTo(returnExpression.resolveBinding()))
							return assignedParameter;
					}
					for(VariableDeclaration assignedLocalVariable : assignedLocalVariables) {
						if(assignedLocalVariable.resolveBinding().isEqualTo(returnExpression.resolveBinding()))
							return assignedLocalVariable;
					}
				}
			}
		}
		return null;
	}
	
	public SimpleName getTypeVariableSimpleName() {
		if(typeField != null) {
			return typeField.getName();
		}
		else if(typeLocalVariable != null) {
