package gr.uom.java.ast.decomposition.matching;

import gr.uom.java.ast.ASTInformationGenerator;
import gr.uom.java.ast.ASTReader;
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.StatementObject;
import gr.uom.java.ast.decomposition.StatementType;
import gr.uom.java.ast.decomposition.cfg.PDGNode;
import gr.uom.java.ast.decomposition.cfg.PlainVariable;
import gr.uom.java.ast.decomposition.matching.conditional.AbstractControlStructure;
import gr.uom.java.ast.decomposition.matching.conditional.AbstractControlStructureUtilities;
import gr.uom.java.ast.decomposition.matching.conditional.IfControlStructure;
import gr.uom.java.ast.decomposition.matching.conditional.SwitchControlStructure;
import gr.uom.java.ast.decomposition.matching.conditional.TernaryControlStructure;
import gr.uom.java.ast.decomposition.matching.loop.AbstractControlVariable;
import gr.uom.java.ast.decomposition.matching.loop.AbstractLoop;
import gr.uom.java.ast.decomposition.matching.loop.AbstractLoopUtilities;
import gr.uom.java.ast.decomposition.matching.loop.ConditionalLoop;
import gr.uom.java.ast.decomposition.matching.loop.ConditionalLoopASTNodeMatcher;
import gr.uom.java.ast.decomposition.matching.loop.ControlVariable;
import gr.uom.java.ast.decomposition.matching.loop.EnhancedForLoop;
import gr.uom.java.ast.inheritance.TypeBindingInheritanceDetection;
import gr.uom.java.ast.util.ExpressionExtractor;
import gr.uom.java.ast.util.MethodDeclarationUtility;
import gr.uom.java.ast.util.math.LevenshteinDistance;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.dom.ASTMatcher;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.ArrayAccess;
import org.eclipse.jdt.core.dom.ArrayCreation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BooleanLiteral;
import org.eclipse.jdt.core.dom.CastExpression;
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.ConditionalExpression;
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.ForStatement;
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.IfStatement;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.InstanceofExpression;
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.NullLiteral;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
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.Statement;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
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.TypeLiteral;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.WhileStatement;

public class ASTNodeMatcher extends ASTMatcher{

	private List<ASTNodeDifference> differences;
	private ITypeRoot typeRoot1;
	private ITypeRoot typeRoot2;
	private List<AbstractMethodFragment> additionallyMatchedFragments1;
	private List<AbstractMethodFragment> additionallyMatchedFragments2;
	
	public ASTNodeMatcher(ITypeRoot root1, ITypeRoot root2) {
		this.differences = new ArrayList<ASTNodeDifference>();
		this.typeRoot1 = root1;
		this.typeRoot2 = root2;
		this.additionallyMatchedFragments1 = new ArrayList<AbstractMethodFragment>();
		this.additionallyMatchedFragments2 = new ArrayList<AbstractMethodFragment>();
	}

	public boolean match(PDGNode nodeG1, PDGNode nodeG2) {
		NodePair pair = new NodePair(nodeG1.getId(), nodeG2.getId());
		NodePairComparisonCache cache = NodePairComparisonCache.getInstance();
		if(cache.containsNodePair(pair)) {
			this.differences.addAll(cache.getDifferencesForNodePair(pair));
			this.additionallyMatchedFragments1.addAll(cache.getAdditionallyMatchedFragments1(pair));
			this.additionallyMatchedFragments2.addAll(cache.getAdditionallyMatchedFragments2(pair));
			return cache.getMatchForNodePair(pair);
		}
		else {
			boolean match = nodeG1.getASTStatement().subtreeMatch(this, nodeG2.getASTStatement());
			cache.addDifferencesForNodePair(pair, this.differences);
			cache.addMatchForNodePair(pair, match);
			cache.setAdditionallyMatchedFragments1(pair, this.additionallyMatchedFragments1);
			cache.setAdditionallyMatchedFragments2(pair, this.additionallyMatchedFragments2);
			return match;
		}
	}

	protected void addDifference(ASTNodeDifference difference) {
		if(!differences.contains(difference))
			differences.add(difference);
	}

	public List<ASTNodeDifference> getDifferences() {
		return differences;
	}

	public ITypeRoot getTypeRoot1() {
		return typeRoot1;
	}

	public ITypeRoot getTypeRoot2() {
		return typeRoot2;
	}

	public List<AbstractMethodFragment> getAdditionallyMatchedFragments1() {
		return additionallyMatchedFragments1;
	}

	public List<AbstractMethodFragment> getAdditionallyMatchedFragments2() {
		return additionallyMatchedFragments2;
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		for(ASTNodeDifference diff : differences) {
			sb.append(diff.toString());
		}
		return sb.toString();
	}

	public boolean isParameterizable() {
		if(onlyVariableTypeMismatchDifferences() || additionallyMatchedFragments1.size() > 0 || additionallyMatchedFragments2.size() > 0)
			return true;
		else if(methodInvocationMatchWithMissingExpressionAndDifferentNameAndDifferentArguments())
			return false;
		else {
			for(ASTNodeDifference diff : differences) {
				boolean expression1NestedUnderCatchClause = isNestedUnderAnonymousClassDeclaration(diff.getExpression1().getExpression());
				boolean expression2NestedUnderCatchClause = isNestedUnderAnonymousClassDeclaration(diff.getExpression2().getExpression());
				if(!diff.isParameterizable() && !expression1NestedUnderCatchClause && !expression2NestedUnderCatchClause)
					return false;
			}
			return true;
		}
	}

	private boolean methodInvocationMatchWithMissingExpressionAndDifferentNameAndDifferentArguments() {
		boolean missingExpression = false;
		boolean differentMethodName = false;
		boolean differentArguments = false;
		boolean expression1NestedUnderCatchClause = false;
		boolean expression2NestedUnderCatchClause = false;
		for(ASTNodeDifference difference : differences) {
			if(difference.containsDifferenceType(DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION))
				missingExpression = true;
			if(difference.containsDifferenceType(DifferenceType.METHOD_INVOCATION_NAME_MISMATCH))
				differentMethodName = true;
			if(difference.containsDifferenceType(DifferenceType.ARGUMENT_NUMBER_MISMATCH) || difference.containsDifferenceType(DifferenceType.TYPE_COMPATIBLE_REPLACEMENT))
				differentArguments = true;
			if(isNestedUnderAnonymousClassDeclaration(difference.getExpression1().getExpression()))
				expression1NestedUnderCatchClause = true;
			if(isNestedUnderAnonymousClassDeclaration(difference.getExpression2().getExpression()))
				expression2NestedUnderCatchClause = true;
		}
		return missingExpression && differentMethodName && differentArguments && !expression1NestedUnderCatchClause && !expression2NestedUnderCatchClause;
	}

	private boolean onlyVariableTypeMismatchDifferences() {
		int diffCount = 0;
		int variableTypeMismatchCount = 0;
		int subclassTypeMismatchCount = 0;
		int variableNameMismatchCount = 0;
		int methodInvocationNameMismatchCount = 0;
		int literalValueMismatchCount = 0;
		for(ASTNodeDifference difference : differences) {
			for(Difference diff : difference.getDifferences()) {
				diffCount++;
				if(diff.getType().equals(DifferenceType.VARIABLE_TYPE_MISMATCH)) {
					variableTypeMismatchCount++;
				}
				if(diff.getType().equals(DifferenceType.SUBCLASS_TYPE_MISMATCH)) {
					subclassTypeMismatchCount++;
				}
				if(diff.getType().equals(DifferenceType.VARIABLE_NAME_MISMATCH)) {
					variableNameMismatchCount++;
				}
				if(diff.getType().equals(DifferenceType.METHOD_INVOCATION_NAME_MISMATCH)) {
					methodInvocationNameMismatchCount++;
				}
				if(diff.getType().equals(DifferenceType.LITERAL_VALUE_MISMATCH)) {
					literalValueMismatchCount++;
				}
			}
		}
		if(diffCount > 0 && (diffCount == (variableTypeMismatchCount + subclassTypeMismatchCount + variableNameMismatchCount) ||
				diffCount == (variableTypeMismatchCount + subclassTypeMismatchCount + methodInvocationNameMismatchCount) ||
				diffCount == (variableTypeMismatchCount + subclassTypeMismatchCount + literalValueMismatchCount) ||
				diffCount == (variableTypeMismatchCount + subclassTypeMismatchCount + literalValueMismatchCount + methodInvocationNameMismatchCount) ||
				diffCount == (variableTypeMismatchCount + subclassTypeMismatchCount + literalValueMismatchCount + variableNameMismatchCount) ))
			return true;
		return false;
	}

	protected boolean isTypeHolder(Object o) {
		if(o.getClass().equals(MethodInvocation.class) || o.getClass().equals(SuperMethodInvocation.class)			
				|| o.getClass().equals(NumberLiteral.class) || o.getClass().equals(StringLiteral.class)
				|| o.getClass().equals(CharacterLiteral.class) || o.getClass().equals(BooleanLiteral.class)
				|| o.getClass().equals(TypeLiteral.class) || o.getClass().equals(NullLiteral.class)
				|| o.getClass().equals(ArrayCreation.class)
				|| o.getClass().equals(ClassInstanceCreation.class)
				|| o.getClass().equals(ArrayAccess.class) || o.getClass().equals(FieldAccess.class)
				|| o.getClass().equals(SuperFieldAccess.class) || o.getClass().equals(ParenthesizedExpression.class)
				|| o.getClass().equals(SimpleName.class) || o.getClass().equals(QualifiedName.class)
				|| o.getClass().equals(CastExpression.class) || o.getClass().equals(InfixExpression.class)
				|| o.getClass().equals(PrefixExpression.class) || o.getClass().equals(InstanceofExpression.class)
				|| o.getClass().equals(ThisExpression.class) || o.getClass().equals(ConditionalExpression.class))
			return true;
		return false;
	}

	protected ITypeBinding getTypeBinding(Object o) {
		if(o.getClass().equals(MethodInvocation.class)) {
			MethodInvocation methodInvocation = (MethodInvocation) o;
			return methodInvocation.resolveMethodBinding().getReturnType();
		}
		else if(o.getClass().equals(SuperMethodInvocation.class)) {
			SuperMethodInvocation superMethodInvocation = (SuperMethodInvocation) o;
			return superMethodInvocation.resolveMethodBinding().getReturnType();
		}
		else if(o.getClass().equals(NumberLiteral.class)) {
			NumberLiteral numberLiteral = (NumberLiteral) o;
			return numberLiteral.resolveTypeBinding();
		}
		else if(o.getClass().equals(StringLiteral.class)) {
			StringLiteral stringLiteral = (StringLiteral) o;
			return stringLiteral.resolveTypeBinding();
		}
		else if(o.getClass().equals(CharacterLiteral.class)) {
			CharacterLiteral characterLiteral = (CharacterLiteral) o;
			return characterLiteral.resolveTypeBinding();
		}
		else if(o.getClass().equals(BooleanLiteral.class)) {
			BooleanLiteral booleanLiteral = (BooleanLiteral) o;
			return booleanLiteral.resolveTypeBinding();
		}
		else if(o.getClass().equals(TypeLiteral.class)) {
			TypeLiteral typeLiteral = (TypeLiteral) o;
			return typeLiteral.resolveTypeBinding();
		}
		else if(o.getClass().equals(ArrayCreation.class)) {
			ArrayCreation arrayCreation = (ArrayCreation) o;
			return arrayCreation.resolveTypeBinding();
		}
		else if(o.getClass().equals(ClassInstanceCreation.class)) {
			ClassInstanceCreation classInstanceCreation = (ClassInstanceCreation) o;
			return classInstanceCreation.resolveTypeBinding();
		}
		else if(o.getClass().equals(ArrayAccess.class)) {
			ArrayAccess arrayAccess = (ArrayAccess) o;
			return arrayAccess.resolveTypeBinding();
		}
		else if(o.getClass().equals(FieldAccess.class)) {
			FieldAccess fieldAccess = (FieldAccess) o;
			return fieldAccess.resolveTypeBinding();
		}
		else if(o.getClass().equals(SuperFieldAccess.class)) {
			SuperFieldAccess superFieldAccess = (SuperFieldAccess) o;
			return superFieldAccess.resolveTypeBinding();
		}
		else if(o.getClass().equals(SimpleName.class)) {
			SimpleName simpleName = (SimpleName) o;
			return simpleName.resolveTypeBinding();
		}
		else if(o.getClass().equals(QualifiedName.class)) {
			QualifiedName qualifiedName = (QualifiedName) o;
			return qualifiedName.resolveTypeBinding();
		}
		else if(o.getClass().equals(CastExpression.class)) {
			CastExpression castExpression = (CastExpression) o;
			return castExpression.resolveTypeBinding();
		}
		else if(o.getClass().equals(InfixExpression.class)) {
			InfixExpression infixExpression = (InfixExpression) o;
			return infixExpression.resolveTypeBinding();
		}
		else if(o.getClass().equals(NullLiteral.class)) {
			NullLiteral nullLiteral = (NullLiteral) o;
			ASTNode parent = ((ASTNode)o).getParent();
			List<Expression> arguments = null;
			IMethodBinding methodBinding = null;
			if(parent instanceof MethodInvocation) {
				MethodInvocation parentMethodInvocation = (MethodInvocation)parent;
				arguments = parentMethodInvocation.arguments();
				methodBinding = parentMethodInvocation.resolveMethodBinding();
			}
			else if(parent instanceof SuperMethodInvocation) {
				SuperMethodInvocation parentMethodInvocation = (SuperMethodInvocation)parent;
				arguments = parentMethodInvocation.arguments();
				methodBinding = parentMethodInvocation.resolveMethodBinding();
			}
			else if(parent instanceof ClassInstanceCreation) {
				ClassInstanceCreation parentClassInstanceCreation = (ClassInstanceCreation)parent;
				arguments = parentClassInstanceCreation.arguments();
				methodBinding = parentClassInstanceCreation.resolveConstructorBinding();
			}
			if(arguments != null && methodBinding != null) {
				int argumentPosition = 0;
				for(Expression argument : arguments) {
					if(argument.equals(o)) {
						ITypeBinding[] parameterTypeBindings = methodBinding.getParameterTypes();
						//analyze only if the argument does not correspond to a varargs parameter
						if(argumentPosition < parameterTypeBindings.length) {
							return parameterTypeBindings[argumentPosition];
						}
					}
					argumentPosition++;
				}
			}
			return nullLiteral.resolveTypeBinding();
		}
		else if(o.getClass().equals(ParenthesizedExpression.class)) {
			ParenthesizedExpression expression = (ParenthesizedExpression) o;
			return expression.resolveTypeBinding();
		}
		else if(o.getClass().equals(PrefixExpression.class)) {
			PrefixExpression expression = (PrefixExpression) o;
			return expression.resolveTypeBinding();
		}
		else if(o.getClass().equals(InstanceofExpression.class)) {
			InstanceofExpression expression = (InstanceofExpression) o;
			return expression.resolveTypeBinding();
		}
		else if(o.getClass().equals(ThisExpression.class)) {
			ThisExpression expression = (ThisExpression) o;
			return expression.resolveTypeBinding();
		}
		else if(o.getClass().equals(ConditionalExpression.class)) {
			ConditionalExpression expression = (ConditionalExpression) o;
			return expression.resolveTypeBinding();
		}
		return null;
	}

	protected boolean typeBindingMatch(ITypeBinding binding1, ITypeBinding binding2) {
		//if bindings are both null then they were recovered from SimpleName expressions representing labels
		if(binding1 == null && binding2 == null)
			return true;
		if(binding1 != null && binding1.isAnonymous() && binding2 != null && binding2.isAnonymous()) {
			ITypeBinding[] interfaces1 = binding1.getInterfaces();
			ITypeBinding[] interfaces2 = binding2.getInterfaces();
			if(interfaces1.length == interfaces2.length) {
				for(int i=0; i<interfaces1.length; i++) {
					ITypeBinding interface1 = interfaces1[i];
					ITypeBinding interface2 = interfaces2[i];
					if(subclassTypeMismatch(interface1, interface2)) {
						return false;
					}
				}
				//all interface bindings are equal
				return true;
			}
			else {
				//different number of implemented interfaces
				return false;
			}
		}
		if(binding1.isCapture() && binding2.isCapture()) {
			ITypeBinding wildcardTypeBinding1 = binding1.getWildcard();
			ITypeBinding wildcardTypeBinding2 = binding2.getWildcard();
			if(wildcardTypeBinding1.isEqualTo(wildcardTypeBinding2) && wildcardTypeBinding1.getQualifiedName().equals(wildcardTypeBinding2.getQualifiedName()))
				return true;
		}
		if(binding1.isParameterizedType() && binding2.isParameterizedType()) {
			ITypeBinding[] typeArguments1 = binding1.getTypeArguments();
			ITypeBinding[] typeArguments2 = binding2.getTypeArguments();
			boolean allTypeArgumentsMatch = true;
			if(typeArguments1.length == typeArguments2.length) {
				int i = 0;
				for(ITypeBinding typeArgument1 : typeArguments1) {
					ITypeBinding typeArgument2 = typeArguments2[i];
					if(!typeBindingMatch(typeArgument1, typeArgument2)) {
						allTypeArgumentsMatch = false;
						break;
					}
					i++;
				}
			}
			else {
				allTypeArgumentsMatch = false;
			}
			ITypeBinding declarationTypeBinding1 = binding1.getTypeDeclaration();
			ITypeBinding declarationTypeBinding2 = binding2.getTypeDeclaration();
			boolean declarationTypeMatch = typeBindingMatch(declarationTypeBinding1, declarationTypeBinding2);
			if(declarationTypeMatch && allTypeArgumentsMatch)
				return true;
		}
		if(binding1.isEqualTo(binding2) && binding1.getQualifiedName().equals(binding2.getQualifiedName()))
			return true;
		if(binding1.getQualifiedName().equals("java.lang.Number") && isNumberPrimitiveType(binding2))
			return true;
		if(isNumberPrimitiveType(binding1) && binding2.getQualifiedName().equals("java.lang.Number"))
			return true;
		if(binding1.getName().equals("float") && binding2.getName().equals("double")) {
			return true;
		}
		if(binding1.getName().equals("double") && binding2.getName().equals("float")) {
			return true;
		}
		if(binding1.getName().equals("int") && binding2.getName().equals("byte")) {
			return true;
		}
		if(binding1.getName().equals("byte") && binding2.getName().equals("int")) {
			return true;
		}
		if(binding1.getName().equals("null") && !binding2.isPrimitive()) {
			return true;
		}
		if(binding2.getName().equals("null") && !binding1.isPrimitive()) {
			return true;
		}
		ITypeBinding commonSuperType = commonSuperType(binding1, binding2);
		return validCommonSuperType(commonSuperType);
	}

	private boolean subclassTypeMismatch(ITypeBinding nodeTypeBinding, ITypeBinding otherTypeBinding) {
		if(nodeTypeBinding.isParameterizedType() && otherTypeBinding.isParameterizedType()) {
			ITypeBinding declarationTypeBinding1 = nodeTypeBinding.getTypeDeclaration();
			ITypeBinding declarationTypeBinding2 = otherTypeBinding.getTypeDeclaration();
			return !declarationTypeBinding1.isEqualTo(declarationTypeBinding2) || !typeBindingMatch(nodeTypeBinding, otherTypeBinding);
		}
		return !nodeTypeBinding.isEqualTo(otherTypeBinding) || !nodeTypeBinding.getQualifiedName().equals(otherTypeBinding.getQualifiedName());
	}

	private static boolean isNumberPrimitiveType(ITypeBinding typeBinding) {
		if(typeBinding.isPrimitive()) {
			String name = typeBinding.getQualifiedName();
			if(name.equals("byte") || name.equals("double") || name.equals("float") || name.equals("int") || name.equals("long") || name.equals("short"))
				return true;
		}
		return false;
	}

	public static boolean validCommonSuperType(ITypeBinding commonSuperType) {
		if(commonSuperType != null && !isTaggingInterface(commonSuperType))
			return true;
		return false;
	}

	public static boolean isTaggingInterface(ITypeBinding typeBinding) {
		return typeBinding.getQualifiedName().equals("java.lang.Object") ||
				typeBinding.getQualifiedName().equals("java.io.Serializable") ||
				typeBinding.getQualifiedName().equals("java.lang.Runnable") ||
				typeBinding.getQualifiedName().equals("java.lang.Comparable") ||
				typeBinding.getQualifiedName().equals("java.lang.Cloneable") ||
				typeBinding.getQualifiedName().equals("java.util.EventListener");
	}

	public static ITypeBinding commonSuperType(ITypeBinding typeBinding1, ITypeBinding typeBinding2) {
		if(typeBinding1.getQualifiedName().equals("java.lang.Number") && isNumberPrimitiveType(typeBinding2))
			return typeBinding1;
		if(isNumberPrimitiveType(typeBinding1) && typeBinding2.getQualifiedName().equals("java.lang.Number"))
			return typeBinding2;
		Set<ITypeBinding> superTypes1 = getAllSuperTypes(typeBinding1);
		Set<ITypeBinding> superTypes2 = getAllSuperTypes(typeBinding2);
		for(ITypeBinding superType2 : superTypes2) {
			if(superType2.getQualifiedName().equals(typeBinding1.getQualifiedName()) || superType2.getErasure().getQualifiedName().equals(typeBinding1.getQualifiedName()) ||
					implementsInterface(superType2, typeBinding1) || implementsInterface(superType2.getErasure(), typeBinding1))
				return typeBinding1;
		}
		for(ITypeBinding superType1 : superTypes1) {
			if(superType1.getQualifiedName().equals(typeBinding2.getQualifiedName()) || superType1.getErasure().getQualifiedName().equals(typeBinding2.getQualifiedName()) ||
					implementsInterface(superType1, typeBinding2) || implementsInterface(superType1.getErasure(), typeBinding2))
				return typeBinding2;
		}
		List<ITypeBinding> typeBindings = new ArrayList<ITypeBinding>();
		for(ITypeBinding superType1 : superTypes1) {
			for(ITypeBinding superType2 : superTypes2) {
				if(superType1.getQualifiedName().equals(superType2.getQualifiedName()) &&
						!superType1.getQualifiedName().equals("java.lang.Object")) {
					addTypeBinding(superType1, typeBindings);
				}
			}
		}
		if(typeBindings.size() > 1) {
			TypeBindingInheritanceDetection inheritanceDetection = new TypeBindingInheritanceDetection(typeBindings);
			Set<String> leaves = inheritanceDetection.getLeavesInDeepestLevels();
			if(leaves.isEmpty()) {
				return typeBindings.get(0);
			}
			else {
				List<ITypeBinding> leafTypeBindings = new ArrayList<ITypeBinding>();
				for(String leaf : leaves) {
					for(ITypeBinding typeBinding : typeBindings) {
						if(leaf.equals(typeBinding.getQualifiedName())) {
							leafTypeBindings.add(typeBinding);
							break;
						}
					}
				}
				//return the first leaf that is a system class, if no system class is found return the first leaf that is a system interface
				for(ITypeBinding leafTypeBinding : leafTypeBindings) {
					if(leafTypeBinding.isClass()) {
						return leafTypeBinding;
					}
				}
				for(ITypeBinding leafTypeBinding : leafTypeBindings) {
					if(leafTypeBinding.isInterface() && ASTReader.getSystemObject().getClassObject(leafTypeBinding.getQualifiedName()) != null) {
						return leafTypeBinding;
					}
				}
				return leafTypeBindings.get(0);
			}
		}
		else if(typeBindings.size() == 1) {
			return typeBindings.get(0);
		}
		else {
			if(superTypes1.size() == 1 && superTypes2.size() == 1) {
				for(ITypeBinding superType1 : superTypes1) {
					for(ITypeBinding superType2 : superTypes2) {
						if(superType1.getQualifiedName().equals("java.lang.Object") &&
								superType2.getQualifiedName().equals("java.lang.Object")) {
							return superType1;
						}
					}
				}
			}
			if(superTypes1.size() == 1) {
				for(ITypeBinding superType1 : superTypes1) {
					if(superType1.getQualifiedName().equals("java.lang.Object"))
						return superType1;
				}
			}
			if(superTypes2.size() == 1) {
				for(ITypeBinding superType2 : superTypes2) {
					if(superType2.getQualifiedName().equals("java.lang.Object"))
						return superType2;
				}
			}
			return null;
		}
	}

	public static boolean implementsInterface(ITypeBinding typeBinding, ITypeBinding interfaceType) {
		ITypeBinding[] implementedInterfaces = typeBinding.getInterfaces();
		for(ITypeBinding implementedInterface : implementedInterfaces) {
			if(implementedInterface.getQualifiedName().equals(interfaceType.getQualifiedName()))
				return true;
		}
		return false;
	}

	private static void addTypeBinding(ITypeBinding typeBinding, List<ITypeBinding> typeBindings) {
		boolean found = false;
		for(ITypeBinding typeBinding2 : typeBindings) {
			if(typeBinding.isEqualTo(typeBinding2) && typeBinding.getQualifiedName().equals(typeBinding2.getQualifiedName())) {
				found = true;
				break;
			}
		}
		if(!found) {
			typeBindings.add(typeBinding);
		}
	}

	private static Set<ITypeBinding> getAllSuperTypes(ITypeBinding typeBinding) {
		Set<ITypeBinding> superTypes = new LinkedHashSet<ITypeBinding>();
		ITypeBinding superTypeBinding = typeBinding.getSuperclass();
		if(superTypeBinding != null) {
			superTypes.add(superTypeBinding);
			superTypes.addAll(getAllSuperTypes(superTypeBinding));
		}
		ITypeBinding[] superInterfaces = typeBinding.getInterfaces();
		for(ITypeBinding superInterface : superInterfaces) {
			superTypes.add(superInterface);
			superTypes.addAll(getAllSuperTypes(superInterface));
		}
		return superTypes;
	}

	protected boolean isInfixExpressionWithCompositeParent(ASTNode node) {
		if(node instanceof InfixExpression &&
				(node.getParent() instanceof IfStatement || node.getParent() instanceof InfixExpression ||
				node.getParent() instanceof WhileStatement || node.getParent() instanceof DoStatement ||
				node.getParent() instanceof ForStatement)) {
			return true;
		}
		return false;
	}

	private void processClassInstanceCreationArguments(List<Expression> nodeArguments, List<Expression> otherArguments,
			ASTNodeDifference astNodeDifference, String nodeToString, String otherToString, boolean identicalConstructorErasureType) {
		if(identicalConstructorErasureType) {
			if(nodeArguments.size() != otherArguments.size()) {
				Difference diff = new Difference(nodeToString,otherToString,DifferenceType.ARGUMENT_NUMBER_MISMATCH);
				int size1 = nodeArguments.size() == 0 ? 1 : nodeArguments.size();
				int size2 = otherArguments.size() == 0 ? 1 : otherArguments.size();
				diff.setWeight(size1 * size2);
				astNodeDifference.addDifference(diff);
			}
			else {
				for(int i=0; i<nodeArguments.size(); i++) {
					int differenceCountBefore = differences.size();
					safeSubtreeMatch(nodeArguments.get(i), otherArguments.get(i));
					reduceWeightOfReversedArguments(differenceCountBefore);
				}
			}
		}
	}

	private void processMethodInvocationArguments(List<Expression> nodeArguments, List<Expression> otherArguments,
			ASTNodeDifference astNodeDifference, String nodeToString, String otherToString, boolean overloadedMethods) {
		if(nodeArguments.size() != otherArguments.size()) {
			Difference diff = new Difference(nodeToString,otherToString,DifferenceType.ARGUMENT_NUMBER_MISMATCH);
			if(!overloadedMethods) {
				int size1 = nodeArguments.size() == 0 ? 1 : nodeArguments.size();
				int size2 = otherArguments.size() == 0 ? 1 : otherArguments.size();
				diff.setWeight(size1 * size2);
			}
			astNodeDifference.addDifference(diff);
		}
		else {
			for(int i=0; i<nodeArguments.size(); i++) {
				int differenceCountBefore = differences.size();
				safeSubtreeMatch(nodeArguments.get(i), otherArguments.get(i));
				reduceWeightOfReversedArguments(differenceCountBefore);
			}
		}
	}

	private boolean overloadedMethods(IMethodBinding methodBinding1, IMethodBinding methodBinding2) {
		List<ITypeBinding> parameterTypes1 = new ArrayList<ITypeBinding>(Arrays.asList(methodBinding1.getParameterTypes()));
		List<ITypeBinding> parameterTypes2 = new ArrayList<ITypeBinding>(Arrays.asList(methodBinding2.getParameterTypes()));
		return methodBinding1.getDeclaringClass().isEqualTo(methodBinding2.getDeclaringClass()) && methodBinding1.getName().equals(methodBinding2.getName()) &&
				(parameterTypes1.containsAll(parameterTypes2) || parameterTypes2.containsAll(parameterTypes1));
	}

	private static boolean isExpressionWithinMethodInvocationArgument(ASTNode expression) {
		ASTNode parent = expression.getParent();
		while(parent != null) {
			if(parent instanceof MethodInvocation) {
				MethodInvocation methodInvocation = (MethodInvocation)parent;
				List<Expression> arguments = methodInvocation.arguments();
				for(Expression argument : arguments) {
					if(argument.equals(expression)) {
						return true;
					}
				}
			}
			else if(parent instanceof SuperMethodInvocation) {
				SuperMethodInvocation methodInvocation = (SuperMethodInvocation)parent;
				List<Expression> arguments = methodInvocation.arguments();
				for(Expression argument : arguments) {
					if(argument.equals(expression)) {
						return true;
					}
				}
			}
			else if(parent instanceof ClassInstanceCreation) {
				ClassInstanceCreation classInstanceCreation = (ClassInstanceCreation)parent;
				List<Expression> arguments = classInstanceCreation.arguments();
				for(Expression argument : arguments) {
					if(argument.equals(expression)) {
						return true;
					}
				}
			}
			expression = parent;
			parent = parent.getParent();
		}
		return false;
	}

	public boolean match(ArrayAccess node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof ArrayAccess)) {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			else {
				ArrayAccess o = (ArrayAccess) other;
				safeSubtreeMatch(node.getArray(), o.getArray());
				safeSubtreeMatch(node.getIndex(), o.getIndex());
			}
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(ArrayCreation node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof ArrayCreation)) {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
			}
			else {
				ArrayCreation o = (ArrayCreation) other;
				if(node.dimensions().size() != o.dimensions().size())
				{
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.ARRAY_DIMENSION_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
				ArrayInitializer initializer1 = node.getInitializer();
				ArrayInitializer initializer2 = o.getInitializer();
				if(initializer1 != null && initializer2 != null) {
					List<Expression> expressions1 = initializer1.expressions();
					List<Expression> expressions2 = initializer2.expressions();
					if(expressions1.size() != expressions2.size()) {
						Difference diff = new Difference(initializer1.toString(),initializer2.toString(),
								DifferenceType.ARRAY_INITIALIZER_EXPRESSION_NUMBER_MISMATCH);
						astNodeDifference.addDifference(diff);
					}
				}
				if((initializer1 == null && initializer2 != null) || (initializer1 != null && initializer2 == null)) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.MISSING_ARRAY_INITIALIZER);
					astNodeDifference.addDifference(diff);
				}
				safeSubtreeMatch(node.getType(), o.getType());
				safeSubtreeListMatch(node.dimensions(), o.dimensions());
				boolean initializerMatch = safeSubtreeMatch(node.getInitializer(), o.getInitializer());
				if(!initializerMatch && initializer1 != null && initializer2 != null) {
					Difference diff = new Difference(initializer1.toString(),initializer2.toString(),
							DifferenceType.ARRAY_INITIALIZER_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
			}
			if(!astNodeDifference.isEmpty())
				addDifference(astNodeDifference);
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(Assignment node, Object other) {
		if(other instanceof MethodInvocation) {
			if(fieldAssignmentReplacedWithSetter(node, (MethodInvocation)other))
				return true;
		}
		return super.match(node, other);
	}

	public boolean match(Block node, Object other) {
		if (!(other instanceof Block)) {
			return false;
		}
		Block o = (Block)other;
		if(isNestedUnderAnonymousClassDeclaration(node) && isNestedUnderAnonymousClassDeclaration(o)) {
			return super.match(node, o);
		}
		return true;
	}

	public boolean match(BooleanLiteral node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof BooleanLiteral)) {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			else 
			{
				BooleanLiteral o = (BooleanLiteral) other;
				if(node.booleanValue() != o.booleanValue())
				{
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.LITERAL_VALUE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(CastExpression node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof CastExpression)) {
				if(typeMatch) {
					if(other instanceof Expression && !(other instanceof NullLiteral)) {
						Expression o = (Expression)other;
						ITypeBinding nodeTypeBinding = node.resolveTypeBinding();
						ITypeBinding otherTypeBinding = o.resolveTypeBinding();
						if(subclassTypeMismatch(nodeTypeBinding, otherTypeBinding)) {
							Difference diff = new Difference(nodeTypeBinding.getQualifiedName(),otherTypeBinding.getQualifiedName(),DifferenceType.SUBCLASS_TYPE_MISMATCH);
							astNodeDifference.addDifference(diff);
						}
					}
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			else 
			{
				CastExpression o = (CastExpression) other;
				safeSubtreeMatch(node.getType(), o.getType());
				safeSubtreeMatch(node.getExpression(), o.getExpression());
			}
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(CatchClause node, Object other) {
		if (!(other instanceof CatchClause)) {
			return false;
		}
		CatchClause o = (CatchClause) other;
		return (
			safeSubtreeMatch(node.getException(), o.getException())
				&& safeSubtreeListMatch(node.getBody().statements(), o.getBody().statements()));
	}

	public boolean match(CharacterLiteral node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof CharacterLiteral)) {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			else 
			{
				CharacterLiteral o = (CharacterLiteral) other;
				if(!node.getEscapedValue().equals(o.getEscapedValue()))
				{
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.LITERAL_VALUE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(ClassInstanceCreation node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		String nodeToString = node.toString();
		String otherToString = other.toString();
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof ClassInstanceCreation)) {
				if(typeMatch) {
					if(other instanceof Expression && !(other instanceof NullLiteral)) {
						Expression o = (Expression)other;
						ITypeBinding nodeTypeBinding = node.resolveTypeBinding();
						ITypeBinding otherTypeBinding = o.resolveTypeBinding();
						if(subclassTypeMismatch(nodeTypeBinding, otherTypeBinding)) {
							Difference diff = new Difference(nodeTypeBinding.getQualifiedName(),otherTypeBinding.getQualifiedName(),DifferenceType.SUBCLASS_TYPE_MISMATCH);
							astNodeDifference.addDifference(diff);
						}
					}
					Difference diff = new Difference(nodeToString,otherToString,DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
				}
				else {
					Difference diff = new Difference(nodeToString,otherToString,DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
			}
			else {
				ClassInstanceCreation o = (ClassInstanceCreation) other;
				int differenceCount = differences.size();
				boolean classTypeMatch = safeSubtreeMatch(node.getType(), o.getType());
				boolean identicalErasureType = node.getType().resolveBinding().getErasure().getQualifiedName().equals(o.getType().resolveBinding().getErasure().getQualifiedName());
				int differenceCountAfterTypeMatch = differences.size();
				List<Expression> nodeArguments = node.arguments();
				List<Expression> otherArguments = o.arguments();
				if(classTypeMatch && differenceCountAfterTypeMatch == differenceCount) {
					processClassInstanceCreationArguments(nodeArguments, otherArguments, astNodeDifference, nodeToString, otherToString, identicalErasureType);
					boolean anonymousClassDeclarationMatch = safeSubtreeMatch(node.getAnonymousClassDeclaration(),o.getAnonymousClassDeclaration());
					//safeSubtreeListMatch(node.arguments(), o.arguments());
					safeSubtreeMatch(node.getExpression(), o.getExpression());
					if(node.getExpression()==null && o.getExpression()!=null) {
						Difference diff = new Difference("",o.getExpression().toString(),DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
						astNodeDifference.addDifference(diff);
					}
					else if(node.getExpression()!=null && o.getExpression()==null) {
						Difference diff = new Difference(node.getExpression().toString(),"",DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
						astNodeDifference.addDifference(diff);
					}
					if(!anonymousClassDeclarationMatch) {
						AnonymousClassDeclaration anounymousClassDeclaration1 = node.getAnonymousClassDeclaration();
						AnonymousClassDeclaration anounymousClassDeclaration2 = o.getAnonymousClassDeclaration();
						if(anounymousClassDeclaration1 == null && anounymousClassDeclaration2 != null) {
							Difference diff = new Difference("",anounymousClassDeclaration2.toString(),
									DifferenceType.ANONYMOUS_CLASS_DECLARATION_MISMATCH);
							astNodeDifference.addDifference(diff);
						}
						else if(anounymousClassDeclaration1 != null && anounymousClassDeclaration2 == null) {
							Difference diff = new Difference(anounymousClassDeclaration1.toString(),"",
									DifferenceType.ANONYMOUS_CLASS_DECLARATION_MISMATCH);
							astNodeDifference.addDifference(diff);
						}
						else if(anounymousClassDeclaration1 != null && anounymousClassDeclaration2 != null) {
							Difference diff = new Difference(anounymousClassDeclaration1.toString(),anounymousClassDeclaration2.toString(),
									DifferenceType.ANONYMOUS_CLASS_DECLARATION_MISMATCH);
							astNodeDifference.addDifference(diff);
						}
					}
				}
				else {
					processClassInstanceCreationArguments(nodeArguments, otherArguments, astNodeDifference, nodeToString, otherToString, identicalErasureType);
				}
			}
			if(!astNodeDifference.isEmpty())
				addDifference(astNodeDifference);
			return typeMatch;
		}
		Difference diff = new Difference(nodeToString,otherToString,DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	private void reduceWeightOfReversedArguments(int differenceCountBefore) {
		//find if a new TYPE_COMPATIBLE_REPLACEMENT difference was added
		ASTNodeDifference typeCompatibleReplacementDifference = null;
		for(int j=differenceCountBefore; j<differences.size(); j++) {
			ASTNodeDifference difference = differences.get(j);
			if(difference.containsOnlyDifferenceType(DifferenceType.TYPE_COMPATIBLE_REPLACEMENT)) {
				typeCompatibleReplacementDifference = difference;
				break;
			}
		}
		if(typeCompatibleReplacementDifference != null) {
			//check if there is a reverse difference in the previously recorded differences
			for(int j=0; j<differenceCountBefore; j++) {
				ASTNodeDifference difference = differences.get(j);
				if(difference.containsOnlyDifferenceType(DifferenceType.TYPE_COMPATIBLE_REPLACEMENT) &&
						difference.getBindingSignaturePair().isReverse(typeCompatibleReplacementDifference.getBindingSignaturePair())) {
					Difference diff = typeCompatibleReplacementDifference.getDifferences().get(0);
					diff.setWeight(1);
					Difference reversedArgumentDifference = difference.getDifferences().get(0);
					reversedArgumentDifference.setWeight(1);
					break;
				}
			}
		}
	}

	public boolean match(ConditionalExpression node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof ConditionalExpression)) {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			else {
				ConditionalExpression o = (ConditionalExpression)other;
				/*if(!node.resolveTypeBinding().isEqualTo(o.resolveTypeBinding()) && typeMatch) {
					Difference diff = new Difference(node.resolveTypeBinding().getName(),o.resolveTypeBinding().getName(),DifferenceType.SUBCLASS_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}*/
				safeSubtreeMatch(node.getExpression(),o.getExpression());
				safeSubtreeMatch(node.getThenExpression(), o.getThenExpression());
				safeSubtreeMatch(node.getElseExpression(), o.getElseExpression());
			}
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(DoStatement node, Object other) {
		if (other instanceof DoStatement)
		{
			DoStatement o = (DoStatement) other;
			if(isNestedUnderAnonymousClassDeclaration(node) && isNestedUnderAnonymousClassDeclaration(o)) {
				return super.match(node, o);
			}
			if (safeSubtreeMatch(node.getExpression(), o.getExpression()))
			{
				return true;
			}
		}
		ConditionalLoop nodeConditionalLoop = new ConditionalLoop(node);
		return loopMatch(nodeConditionalLoop, other);
	}

	public boolean match(EnhancedForStatement node, Object other) {
		if (other instanceof EnhancedForStatement)
		{
			EnhancedForStatement o = (EnhancedForStatement) other;
			if(isNestedUnderAnonymousClassDeclaration(node) && isNestedUnderAnonymousClassDeclaration(o)) {
				return super.match(node, o);
			}
			boolean paramMatch = safeSubtreeMatch(node.getParameter(), o.getParameter());
			boolean expMatch = safeSubtreeMatch(node.getExpression(), o.getExpression());
			if (paramMatch && expMatch)
			{
				return true;
			}
		}
		EnhancedForLoop nodeEnhancedForLoop = new EnhancedForLoop(node);
		return loopMatch(nodeEnhancedForLoop, other);
	}

	public boolean match(ExpressionStatement node, Object other) {
		if (AbstractControlStructureUtilities.hasOneConditionalExpression(node) != null && other instanceof IfStatement)
		{
			TernaryControlStructure nodeTernaryControlStructure = new TernaryControlStructure(node);
			return ifMatch(nodeTernaryControlStructure, other);
		}
		else if(node.getExpression() instanceof Assignment && other instanceof VariableDeclarationStatement) {
			VariableDeclarationStatement variableDeclarationStatement = (VariableDeclarationStatement)other;
			List fragments = variableDeclarationStatement.fragments();
			if(fragments.size() == 1) {
				VariableDeclarationFragment fragment = (VariableDeclarationFragment)fragments.get(0);
				Assignment assignment = (Assignment)node.getExpression();
				Expression leftHandSide = assignment.getLeftHandSide();
				if(leftHandSide instanceof SimpleName) {
					SimpleName simpleName = (SimpleName)leftHandSide;
					boolean variableMatch = safeSubtreeMatch(simpleName, fragment.getName());
					boolean variableTypeMatch = false;
					IBinding simpleNameBinding = simpleName.resolveBinding();
					IBinding fragmentNameBinding = fragment.getName().resolveBinding();
					if(simpleNameBinding.getKind() == IBinding.VARIABLE && fragmentNameBinding.getKind() == IBinding.VARIABLE) {
						IVariableBinding simpleNameVariableBinding = (IVariableBinding)simpleNameBinding;
						IVariableBinding fragmentNameVariableBinding = (IVariableBinding)fragmentNameBinding;
						variableTypeMatch = simpleNameVariableBinding.getType().isEqualTo(fragmentNameVariableBinding.getType()) &&
								simpleNameVariableBinding.getType().getQualifiedName().equals(fragmentNameVariableBinding.getType().getQualifiedName());
					}
					boolean initializerMatch = false;
					boolean initializerTypeMatch = false;
					Expression initializer = fragment.getInitializer();
					Expression rightHandSide = assignment.getRightHandSide();
					if(initializer != null && initializer.getNodeType() == rightHandSide.getNodeType()) {
						initializerMatch = safeSubtreeMatch(rightHandSide, initializer);
						initializerTypeMatch = initializer.resolveTypeBinding().isEqualTo(rightHandSide.resolveTypeBinding()) &&
								initializer.resolveTypeBinding().getQualifiedName().equals(rightHandSide.resolveTypeBinding().getQualifiedName());
					}
					if(variableMatch && variableTypeMatch && initializerMatch && initializerTypeMatch) {
						VariableDeclaration variableDeclaration = AbstractLoopUtilities.getVariableDeclaration(simpleName);
						if(variableDeclaration != null && hasEmptyInitializer(variableDeclaration)) {
							safeSubtreeMatch(variableDeclaration.getName(), fragment.getName());
							List<ASTNode> astNodes = new ArrayList<ASTNode>();
							astNodes.add(variableDeclaration);
							ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
							reportAdditionalFragments(astNodes, this.additionallyMatchedFragments1);
							return true;
						}
					}
				}
			}
		}
		return super.match(node, other);
	}

	public boolean match(FieldAccess node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		if(other instanceof MethodInvocation) {
			MethodInvocation methodInvocation = (MethodInvocation)other;
			if(getterMethodForField(methodInvocation, node.getName())) {
				FieldAccessReplacedWithGetterInvocationDifference astNodeDifference = 
						new FieldAccessReplacedWithGetterInvocationDifference(exp1, exp2, methodInvocation.getName().getIdentifier());
				int size = differences.size();
				safeSubtreeMatch(node.getExpression(), methodInvocation.getExpression());
				for(int i=size; i<differences.size(); i++) {
					astNodeDifference.addInvokerDifference(differences.get(i));
				}
				if(node.getExpression()==null && methodInvocation.getExpression()!=null) {
					Difference diff = new Difference("",methodInvocation.getExpression().toString(),DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
					astNodeDifference.addDifference(diff);
				}
				else if(node.getExpression()!=null && methodInvocation.getExpression()==null) {
					Difference diff = new Difference(node.getExpression().toString(),"",DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
					astNodeDifference.addDifference(diff);
				}
				if(node.getExpression()!=null) {
					ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
					astNodeDifference.setInvoker1(new AbstractExpression(node.getExpression()));
				}
				if(methodInvocation.getExpression()!=null) {
					ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
					astNodeDifference.setInvoker2(new AbstractExpression(methodInvocation.getExpression()));
				}
				Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.FIELD_ACCESS_REPLACED_WITH_GETTER);
				astNodeDifference.addDifference(diff);
				addDifference(astNodeDifference);
				return true;
			}
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (other instanceof FieldAccess) {
				FieldAccess o = (FieldAccess) other;
				if(!node.getName().toString().equals(o.getName().toString())) {
					Difference diff = new Difference(node.getName().toString(),o.getName().toString(),DifferenceType.VARIABLE_NAME_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
				ITypeBinding nodeTypeBinding = node.resolveTypeBinding();
				ITypeBinding otherTypeBinding = o.resolveTypeBinding();
				if(subclassTypeMismatch(nodeTypeBinding, otherTypeBinding) && typeMatch) {
					Difference diff = new Difference(nodeTypeBinding.getQualifiedName(),otherTypeBinding.getQualifiedName(),DifferenceType.SUBCLASS_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
				if(!typeMatch) {
					Difference diff = new Difference(node.resolveTypeBinding().getQualifiedName(),o.resolveTypeBinding().getQualifiedName(),DifferenceType.VARIABLE_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
				safeSubtreeMatch(node.getExpression(), o.getExpression());
			}
			else {
				if(typeMatch) {
					if(other instanceof Expression && !(other instanceof NullLiteral)) {
						Expression o = (Expression)other;
						ITypeBinding nodeTypeBinding = node.resolveTypeBinding();
						ITypeBinding otherTypeBinding = o.resolveTypeBinding();
						if(subclassTypeMismatch(nodeTypeBinding, otherTypeBinding)) {
							Difference diff = new Difference(nodeTypeBinding.getQualifiedName(),otherTypeBinding.getQualifiedName(),DifferenceType.SUBCLASS_TYPE_MISMATCH);
							astNodeDifference.addDifference(diff);
						}
					}
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
			}
			if(!astNodeDifference.isEmpty())
				addDifference(astNodeDifference);
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(ForStatement node, Object other) {
		if (other instanceof ForStatement)
		{
			ForStatement o = (ForStatement) other;
			if(isNestedUnderAnonymousClassDeclaration(node) && isNestedUnderAnonymousClassDeclaration(o)) {
				return super.match(node, o);
			}
			boolean initializerMatch = safeSubtreeListMatch(node.initializers(), o.initializers());
			boolean expMatch = safeSubtreeMatch(node.getExpression(), o.getExpression());
			boolean updaterMatch = safeSubtreeListMatch(node.updaters(), o.updaters());
			if (initializerMatch && expMatch && updaterMatch)
			{
				return true;
			}
		}
		ConditionalLoop nodeConditionalLoop = new ConditionalLoop(node);
		return loopMatch(nodeConditionalLoop, other);
	}
	
	public boolean match(IfStatement node, Object other) {
		if (!(other instanceof IfStatement)) {
			if(other instanceof ExpressionStatement)
			{
				ExpressionStatement otherExpressionStatement = (ExpressionStatement)other;
				if (AbstractControlStructureUtilities.hasOneConditionalExpression(otherExpressionStatement) != null)
				{
					IfControlStructure nodeIfControlStructure = new IfControlStructure(node);
					return ifMatch(nodeIfControlStructure, other);
				}
			}
			else if (other instanceof ReturnStatement)
			{
				ReturnStatement otherReturnStatement = (ReturnStatement)other;
				if (otherReturnStatement.getExpression() instanceof ConditionalExpression)
				{
					IfControlStructure nodeIfControlStructure = new IfControlStructure(node);
					return ifMatch(nodeIfControlStructure, other);
				}
			}
			return false;
		}
		IfStatement o = (IfStatement) other;
		if(isNestedUnderAnonymousClassDeclaration(node) && isNestedUnderAnonymousClassDeclaration(o)) {
			return super.match(node, o);
		}
		return (
			safeSubtreeMatch(node.getExpression(), o.getExpression()));
	}

	public boolean match(InfixExpression node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		//if only one of them is infix expression with a composite parent, not both
		boolean infix1 = isInfixExpressionWithCompositeParent(node);
		boolean infix2 = isInfixExpressionWithCompositeParent((ASTNode)other);
		if((infix1 && !infix2) || (!infix1 && infix2)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof InfixExpression)) {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
			}
			else 
			{
				InfixExpression o = (InfixExpression) other;
				// be careful not to trigger lazy creation of extended operand lists
				if (node.hasExtendedOperands() && o.hasExtendedOperands()) {
					if(node.extendedOperands().size() != o.extendedOperands().size()) {
						Difference diff = new Difference(node.toString(),o.toString(),DifferenceType.INFIX_EXTENDED_OPERAND_NUMBER_MISMATCH);
						astNodeDifference.addDifference(diff);
					}
					else {
						safeSubtreeListMatch(node.extendedOperands(), o.extendedOperands());
					}
				}
				if (node.hasExtendedOperands() != o.hasExtendedOperands()) {
					Difference diff = new Difference(node.toString(),o.toString(),DifferenceType.INFIX_EXTENDED_OPERAND_NUMBER_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
				if(!node.getOperator().equals(o.getOperator())) {
					Difference diff = new Difference(node.getOperator().toString(),o.getOperator().toString(),DifferenceType.OPERATOR_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
				int differenceCountBefore = differences.size();
				boolean leftOperandMatch = safeSubtreeMatch(node.getLeftOperand(), o.getLeftOperand());
				int differenceCountAfterLeftOperandMatch = differences.size();
				boolean rightOperandMatch = safeSubtreeMatch(node.getRightOperand(), o.getRightOperand());
				int differenceCountAfterRightOperandMatch = differences.size();
				if(!leftOperandMatch && !rightOperandMatch) {
					//if both left and right operands do not match, then the entire infix expression should be parameterized
					if(differenceCountAfterLeftOperandMatch == differenceCountBefore) {
						Difference leftDiff = new Difference(node.getLeftOperand().toString(),o.getLeftOperand().toString(),DifferenceType.INFIX_LEFT_OPERAND_MISMATCH);
						astNodeDifference.addDifference(leftDiff);
					}
					if(differenceCountAfterRightOperandMatch == differenceCountAfterLeftOperandMatch) {
						Difference rightDiff = new Difference(node.getRightOperand().toString(),o.getRightOperand().toString(),DifferenceType.INFIX_RIGHT_OPERAND_MISMATCH);
						astNodeDifference.addDifference(rightDiff);
					}
				}
				else if(!leftOperandMatch && rightOperandMatch) {
					//if only the left operand does not match, then the left operand should be parameterized
					if(node.getLeftOperand() instanceof InfixExpression || o.getLeftOperand() instanceof InfixExpression) {
						Difference leftOperandDiff = new Difference(node.getLeftOperand().toString(),o.getLeftOperand().toString(),DifferenceType.INFIX_LEFT_OPERAND_MISMATCH);
						ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
						AbstractExpression leftOp1 = new AbstractExpression(node.getLeftOperand());
						ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
						AbstractExpression leftOp2 = new AbstractExpression(o.getLeftOperand());
						ASTNodeDifference astLeftOperandDifference = new ASTNodeDifference(leftOp1, leftOp2);
						astLeftOperandDifference.addDifference(leftOperandDiff);
						addDifference(astLeftOperandDifference);
					}
				}
				else if(leftOperandMatch && !rightOperandMatch) {
					//if only the right operand does not match, then the right operand should be parameterized
					if(node.getRightOperand() instanceof InfixExpression || o.getRightOperand() instanceof InfixExpression) {
						Difference rightOperandDiff = new Difference(node.getRightOperand().toString(),o.getRightOperand().toString(),DifferenceType.INFIX_RIGHT_OPERAND_MISMATCH);
						ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
						AbstractExpression rightOp1 = new AbstractExpression(node.getRightOperand());
						ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
						AbstractExpression rightOp2 = new AbstractExpression(o.getRightOperand());
						ASTNodeDifference astRightOperandDifference = new ASTNodeDifference(rightOp1, rightOp2);
						astRightOperandDifference.addDifference(rightOperandDiff);
						addDifference(astRightOperandDifference);
					}
				}
			}
			if(!astNodeDifference.isEmpty())
				addDifference(astNodeDifference);
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(InstanceofExpression node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof InstanceofExpression)) {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			else 
			{
				InstanceofExpression o = (InstanceofExpression) other;
				safeSubtreeMatch(node.getLeftOperand(), o.getLeftOperand());
				safeSubtreeMatch(node.getRightOperand(), o.getRightOperand());
			}
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(LabeledStatement node, Object other) {
		if (!(other instanceof LabeledStatement)) {
			return false;
		}
		LabeledStatement o = (LabeledStatement) other;
		if(isNestedUnderAnonymousClassDeclaration(node) && isNestedUnderAnonymousClassDeclaration(o)) {
			return super.match(node, o);
		}
		return (
				safeSubtreeMatch(node.getLabel(), o.getLabel()));
	}

	public boolean match(MethodInvocation node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		if(other instanceof Assignment) {
			if(setterReplacedWithFieldAssignment(node, (Assignment)other)) {
				return true;
			}
		}
		String nodeToString = node.toString();
		String otherToString = other.toString();
		if(other instanceof FieldAccess) {
			FieldAccess fieldAccess = (FieldAccess)other;
			if(getterMethodForField(node, fieldAccess.getName())) {
				FieldAccessReplacedWithGetterInvocationDifference astNodeDifference = 
						new FieldAccessReplacedWithGetterInvocationDifference(exp1, exp2, node.getName().getIdentifier());
				int size = differences.size();
				safeSubtreeMatch(node.getExpression(), fieldAccess.getExpression());
				for(int i=size; i<differences.size(); i++) {
					astNodeDifference.addInvokerDifference(differences.get(i));
				}
				if(node.getExpression()==null && fieldAccess.getExpression()!=null) {
					Difference diff = new Difference("",fieldAccess.getExpression().toString(),DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
					astNodeDifference.addDifference(diff);
				}
				else if(node.getExpression()!=null && fieldAccess.getExpression()==null) {
					Difference diff = new Difference(node.getExpression().toString(),"",DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
					astNodeDifference.addDifference(diff);
				}
				if(node.getExpression()!=null) {
					ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
					astNodeDifference.setInvoker1(new AbstractExpression(node.getExpression()));
				}
				if(fieldAccess.getExpression()!=null) {
					ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
					astNodeDifference.setInvoker2(new AbstractExpression(fieldAccess.getExpression()));
				}
				Difference diff = new Difference(nodeToString,otherToString,DifferenceType.FIELD_ACCESS_REPLACED_WITH_GETTER);
				astNodeDifference.addDifference(diff);
				addDifference(astNodeDifference);
				return true;
			}
		}
		if(other instanceof SimpleName) {
			SimpleName simpleName = (SimpleName)other;
			IBinding binding = simpleName.resolveBinding();
			if(binding.getKind() == IBinding.VARIABLE) {
				IVariableBinding variableBinding = (IVariableBinding)binding;
				if(variableBinding.isField()) {
					if(getterMethodForField(node, simpleName)) {
						FieldAccessReplacedWithGetterInvocationDifference astNodeDifference = 
								new FieldAccessReplacedWithGetterInvocationDifference(exp1, exp2, node.getName().getIdentifier());
						if(node.getExpression() != null) {
							Difference diff = new Difference(node.getExpression().toString(),"",DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
							astNodeDifference.addDifference(diff);
							ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
							astNodeDifference.setInvoker1(new AbstractExpression(node.getExpression()));
						}
						Difference diff = new Difference(nodeToString,otherToString,DifferenceType.FIELD_ACCESS_REPLACED_WITH_GETTER);
						astNodeDifference.addDifference(diff);
						addDifference(astNodeDifference);
						return true;
					}
				}
			}
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveMethodBinding().getReturnType(), getTypeBinding(other));
			if (!(other instanceof MethodInvocation)) {
				if(typeMatch) {
					if(other instanceof Expression && !(other instanceof NullLiteral)) {
						Expression o = (Expression)other;
						ITypeBinding nodeTypeBinding = node.resolveTypeBinding();
						ITypeBinding otherTypeBinding = o.resolveTypeBinding();
						if(subclassTypeMismatch(nodeTypeBinding, otherTypeBinding)) {
							Difference diff = new Difference(nodeTypeBinding.getQualifiedName(),otherTypeBinding.getQualifiedName(),DifferenceType.SUBCLASS_TYPE_MISMATCH);
							astNodeDifference.addDifference(diff);
						}
					}
					Difference diff = new Difference(nodeToString,otherToString,DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
				}
				else {
					Difference diff = new Difference(nodeToString,otherToString,DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
			}
			else {
				MethodInvocation o = (MethodInvocation) other;
				IMethodBinding nodeMethodBinding = node.resolveMethodBinding();
				IMethodBinding otherMethodBinding = o.resolveMethodBinding();
				boolean isNodeMethodBindingStatic = (nodeMethodBinding.getModifiers() & Modifier.STATIC) != 0;
				boolean isOtherMethodBindingStatic = (otherMethodBinding.getModifiers() & Modifier.STATIC) != 0;
				if(isNodeMethodBindingStatic != isOtherMethodBindingStatic) {
					if(typeMatch) {
						Difference diff = new Difference(nodeToString,otherToString,DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
						astNodeDifference.addDifference(diff);
						if(!(node.getParent() instanceof Statement) && !(o.getParent() instanceof Statement)) {
							addDifference(astNodeDifference);
							return typeMatch;
						}
					}
					else {
						Difference diff = new Difference(nodeToString,otherToString,DifferenceType.AST_TYPE_MISMATCH);
						astNodeDifference.addDifference(diff);
						if(!(node.getParent() instanceof Statement) && !(o.getParent() instanceof Statement)) {
							addDifference(astNodeDifference);
							return typeMatch;
						}
					}
				}
				if(isExpressionWithinMethodInvocationArgument(node) && isExpressionWithinMethodInvocationArgument(o) &&
						node.getExpression() != null && o.getExpression() != null &&
						node.getExpression().getNodeType() != o.getExpression().getNodeType() &&
						node.arguments().isEmpty() && o.arguments().isEmpty() &&
						typeBindingMatch(node.resolveMethodBinding().getReturnType(), o.resolveMethodBinding().getReturnType())) {
					Difference diff = new Difference(nodeToString,otherToString,DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
				}
				else {
					List<Expression> nodeArguments = node.arguments();
					List<Expression> otherArguments = o.arguments();
					processMethodInvocationArguments(nodeArguments, otherArguments, astNodeDifference, nodeToString, otherToString, overloadedMethods(nodeMethodBinding, otherMethodBinding));
					safeSubtreeMatch(node.getName(), o.getName());
					//safeSubtreeListMatch(node.arguments(), o.arguments());
					safeSubtreeMatch(node.getExpression(), o.getExpression());
					if(node.getExpression()==null && o.getExpression()!=null) {
						Difference diff = new Difference("",o.getExpression().toString(),DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
						astNodeDifference.addDifference(diff);
					}
					else if(node.getExpression()!=null && o.getExpression()==null) {
						Difference diff = new Difference(node.getExpression().toString(),"",DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
						astNodeDifference.addDifference(diff);
					}
				}
			}
			if(!astNodeDifference.isEmpty())
				addDifference(astNodeDifference);
			return typeMatch;
		}
		Difference diff = new Difference(nodeToString,otherToString,DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(NullLiteral node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof NullLiteral)) {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			else {
				return true;
			}
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(NumberLiteral node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof NumberLiteral)) {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
			}
			else 
			{
				NumberLiteral o = (NumberLiteral) other;
				if(!node.getToken().equals(o.getToken()))
				{
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.LITERAL_VALUE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
				if(!typeMatch) {
					Difference diff = new Difference(node.resolveTypeBinding().getQualifiedName(),o.resolveTypeBinding().getQualifiedName(),DifferenceType.VARIABLE_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
			}
			if(!astNodeDifference.isEmpty())
				addDifference(astNodeDifference);
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(ParenthesizedExpression node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof ParenthesizedExpression)) {
				if(typeMatch) {
					if(other instanceof Expression && !(other instanceof NullLiteral)) {
						Expression o = (Expression)other;
						ITypeBinding nodeTypeBinding = node.resolveTypeBinding();
						ITypeBinding otherTypeBinding = o.resolveTypeBinding();
						if(subclassTypeMismatch(nodeTypeBinding, otherTypeBinding)) {
							Difference diff = new Difference(nodeTypeBinding.getQualifiedName(),otherTypeBinding.getQualifiedName(),DifferenceType.SUBCLASS_TYPE_MISMATCH);
							astNodeDifference.addDifference(diff);
						}
					}
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			else {
				ParenthesizedExpression o = (ParenthesizedExpression) other;
				int differenceCountBefore = differences.size();
				boolean expressionMatch = safeSubtreeMatch(node.getExpression(), o.getExpression());
				int differenceCountAfter = differences.size();
				if(!expressionMatch && typeMatch && differenceCountAfter == differenceCountBefore) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(PrefixExpression node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof PrefixExpression)) {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			else {
				PrefixExpression o = (PrefixExpression) other;
				if(!node.getOperator().equals(o.getOperator())) {
					Difference diff = new Difference(node.getOperator().toString(),o.getOperator().toString(),DifferenceType.OPERATOR_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
				safeSubtreeMatch(node.getOperand(), o.getOperand());
			}
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(QualifiedName node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch;
			if(node.resolveTypeBinding() == null || getTypeBinding(other) == null)
			{
				if (other instanceof QualifiedName) {
					QualifiedName o = (QualifiedName) other;
					if(!node.getName().toString().equals(o.getName().toString())) {
						Difference diff = new Difference(node.getName().toString(),o.getName().toString(),DifferenceType.VARIABLE_NAME_MISMATCH);
						astNodeDifference.addDifference(diff);
					}
				}
				typeMatch = true;
			}
			else {
				typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
				if (other instanceof QualifiedName) {
					QualifiedName o = (QualifiedName) other;
					if(!node.getName().toString().equals(o.getName().toString())) {
						Difference diff = new Difference(node.getName().toString(),o.getName().toString(),DifferenceType.VARIABLE_NAME_MISMATCH);
						astNodeDifference.addDifference(diff);
					}
					ITypeBinding nodeTypeBinding = node.resolveTypeBinding();
					ITypeBinding otherTypeBinding = o.resolveTypeBinding();
					if(subclassTypeMismatch(nodeTypeBinding, otherTypeBinding) && typeMatch) {
						Difference diff = new Difference(nodeTypeBinding.getQualifiedName(),otherTypeBinding.getQualifiedName(),DifferenceType.SUBCLASS_TYPE_MISMATCH);
						astNodeDifference.addDifference(diff);
					}
					if(!typeMatch) {
						Difference diff = new Difference(nodeTypeBinding.getQualifiedName(),otherTypeBinding.getQualifiedName(),DifferenceType.VARIABLE_TYPE_MISMATCH);
						astNodeDifference.addDifference(diff);
					}
					safeSubtreeMatch(node.getQualifier(), o.getQualifier());
				}
				else {
					if(typeMatch) {
						if(other instanceof Expression && !(other instanceof NullLiteral)) {
							Expression o = (Expression)other;
							ITypeBinding nodeTypeBinding = node.resolveTypeBinding();
							ITypeBinding otherTypeBinding = o.resolveTypeBinding();
							if(subclassTypeMismatch(nodeTypeBinding, otherTypeBinding)) {
								Difference diff = new Difference(nodeTypeBinding.getQualifiedName(),otherTypeBinding.getQualifiedName(),DifferenceType.SUBCLASS_TYPE_MISMATCH);
								astNodeDifference.addDifference(diff);
							}
						}
						Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
						astNodeDifference.addDifference(diff);
					}
					else {
						Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
						astNodeDifference.addDifference(diff);
					}
				}
			}
			if(!astNodeDifference.isEmpty())
				addDifference(astNodeDifference);
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(ReturnStatement node, Object other) {
		if (node.getExpression() instanceof ConditionalExpression && other instanceof IfStatement)
		{
			TernaryControlStructure nodeTernaryControlStructure = new TernaryControlStructure(node);
			return ifMatch(nodeTernaryControlStructure, other);
		}
		return super.match(node, other);
	}

	public boolean match(SimpleName node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		if(other instanceof MethodInvocation) {
			MethodInvocation methodInvocation = (MethodInvocation)other;
			IBinding binding = node.resolveBinding();
			if(binding.getKind() == IBinding.VARIABLE) {
				IVariableBinding variableBinding = (IVariableBinding)binding;
				if(variableBinding.isField()) {
					if(getterMethodForField(methodInvocation, node)) {
						FieldAccessReplacedWithGetterInvocationDifference astNodeDifference = 
								new FieldAccessReplacedWithGetterInvocationDifference(exp1, exp2, methodInvocation.getName().getIdentifier());
						if(methodInvocation.getExpression() != null) {
							Difference diff = new Difference("",methodInvocation.getExpression().toString(),DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
							astNodeDifference.addDifference(diff);
							ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
							astNodeDifference.setInvoker2(new AbstractExpression(methodInvocation.getExpression()));
						}
						Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.FIELD_ACCESS_REPLACED_WITH_GETTER);
						astNodeDifference.addDifference(diff);
						addDifference(astNodeDifference);
						return true;
					}
				}
			}
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (other instanceof SimpleName) {
				SimpleName o = (SimpleName) other;
				IBinding nodeBinding = node.resolveBinding();
				IBinding otherBinding = o.resolveBinding();
				if(nodeBinding != null && otherBinding != null) {
					if(nodeBinding.getKind() == IBinding.METHOD && otherBinding.getKind() == IBinding.METHOD) {
						if(!node.getIdentifier().equals(o.getIdentifier())) {
							Difference diff = new Difference(node.getIdentifier(),o.getIdentifier(),DifferenceType.METHOD_INVOCATION_NAME_MISMATCH);
							diff.setWeight(LevenshteinDistance.computeLevenshteinDistance(node.getIdentifier(),o.getIdentifier()));
							astNodeDifference.addDifference(diff);
						}
					}
					else if(nodeBinding.getKind() == IBinding.TYPE && otherBinding.getKind() == IBinding.TYPE) {
						ITypeBinding nodeTypeBinding = (ITypeBinding)nodeBinding;
						ITypeBinding otherTypeBinding = (ITypeBinding)otherBinding;
						if(subclassTypeMismatch(nodeTypeBinding, otherTypeBinding) && typeMatch) {
							Difference diff = new Difference(nodeTypeBinding.getQualifiedName(),
									otherTypeBinding.getQualifiedName(),DifferenceType.SUBCLASS_TYPE_MISMATCH);
							astNodeDifference.addDifference(diff);
						}
					}
					else if(nodeBinding.getKind() == IBinding.VARIABLE && otherBinding.getKind() == IBinding.VARIABLE) {
						if(!node.getIdentifier().equals(o.getIdentifier())) {
							Difference diff = new Difference(node.getIdentifier(),o.getIdentifier(),DifferenceType.VARIABLE_NAME_MISMATCH);
							astNodeDifference.addDifference(diff);
						}
					}
				}
				if(!typeMatch) {
					Difference diff = new Difference(node.resolveTypeBinding().getQualifiedName(),o.resolveTypeBinding().getQualifiedName(),DifferenceType.VARIABLE_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
				else {
					if(nodeBinding != null && otherBinding != null && nodeBinding.getKind() == IBinding.VARIABLE && otherBinding.getKind() == IBinding.VARIABLE) {
						IVariableBinding nodeVariableBinding = (IVariableBinding)nodeBinding;
						IVariableBinding otherVariableBinding = (IVariableBinding)otherBinding;
						ITypeBinding nodeTypeBinding = nodeVariableBinding.getType();
						ITypeBinding otherTypeBinding = otherVariableBinding.getType();
						if(nodeTypeBinding != null && otherTypeBinding != null && subclassTypeMismatch(nodeTypeBinding, otherTypeBinding)) {
							Difference diff = new Difference(nodeTypeBinding.getQualifiedName(),otherTypeBinding.getQualifiedName(),DifferenceType.SUBCLASS_TYPE_MISMATCH);
							astNodeDifference.addDifference(diff);
						}
					}
				}
			}
			else {
				if(typeMatch) {
					if(other instanceof Expression && !(other instanceof NullLiteral)) {
						Expression o = (Expression)other;
						ITypeBinding nodeTypeBinding = node.resolveTypeBinding();
						ITypeBinding otherTypeBinding = o.resolveTypeBinding();
						if(subclassTypeMismatch(nodeTypeBinding, otherTypeBinding)) {
							Difference diff = new Difference(nodeTypeBinding.getQualifiedName(),otherTypeBinding.getQualifiedName(),DifferenceType.SUBCLASS_TYPE_MISMATCH);
							astNodeDifference.addDifference(diff);
						}
					}
					Difference diff = new Difference(node.getIdentifier(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
				}
				else {
					Difference diff = new Difference(node.getIdentifier(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
			}
			if(!astNodeDifference.isEmpty())
				addDifference(astNodeDifference);
			return typeMatch;
		}
		Difference diff = new Difference(node.getIdentifier(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(StringLiteral node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof StringLiteral)) {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			else 
			{
				StringLiteral o = (StringLiteral) other;
				if(!node.getLiteralValue().equals(o.getLiteralValue()))
				{
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.LITERAL_VALUE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(SuperFieldAccess node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (other instanceof SuperFieldAccess) {
				SuperFieldAccess o = (SuperFieldAccess) other;
				if(!node.getName().toString().equals(o.getName().toString())) {
					Difference diff = new Difference(node.getName().toString(),o.getName().toString(),DifferenceType.VARIABLE_NAME_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
				ITypeBinding nodeTypeBinding = node.resolveTypeBinding();
				ITypeBinding otherTypeBinding = o.resolveTypeBinding();
				if(subclassTypeMismatch(nodeTypeBinding, otherTypeBinding) && typeMatch) {
					Difference diff = new Difference(nodeTypeBinding.getQualifiedName(),otherTypeBinding.getQualifiedName(),DifferenceType.SUBCLASS_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
				if(!typeMatch) {
					Difference diff = new Difference(node.resolveTypeBinding().getQualifiedName(),o.resolveTypeBinding().getQualifiedName(),DifferenceType.VARIABLE_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
			}
			else {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
			}
			if(!astNodeDifference.isEmpty())
				addDifference(astNodeDifference);
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(SuperMethodInvocation node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		String nodeToString = node.toString();
		String otherToString = other.toString();
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveMethodBinding().getReturnType(), getTypeBinding(other));
			if (!(other instanceof SuperMethodInvocation)) {
				if(typeMatch) {
					Difference diff = new Difference(nodeToString,otherToString,DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
				}
				else {
					Difference diff = new Difference(nodeToString,otherToString,DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
				}
			}
			else {
				SuperMethodInvocation o = (SuperMethodInvocation) other;
				IMethodBinding nodeMethodBinding = node.resolveMethodBinding();
				IMethodBinding otherMethodBinding = o.resolveMethodBinding();
				List<Expression> nodeArguments = node.arguments();
				List<Expression> otherArguments = o.arguments();
				processMethodInvocationArguments(nodeArguments, otherArguments, astNodeDifference, nodeToString, otherToString, overloadedMethods(nodeMethodBinding, otherMethodBinding));
				safeSubtreeMatch(node.getName(), o.getName());
				//safeSubtreeListMatch(node.arguments(), o.arguments());
				safeSubtreeMatch(node.getQualifier(), o.getQualifier());
			}
			if(!astNodeDifference.isEmpty())
				addDifference(astNodeDifference);
			return typeMatch;
		}
		Difference diff = new Difference(nodeToString,otherToString,DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(SwitchStatement node, Object other) {
		if (!(other instanceof SwitchStatement)) {
			return false;
		}
		SwitchStatement o = (SwitchStatement) other;
		if(isNestedUnderAnonymousClassDeclaration(node) && isNestedUnderAnonymousClassDeclaration(o)) {
			return super.match(node, o);
		}
		SwitchControlStructure nodeSwitchStructure = new SwitchControlStructure(node);
		SwitchControlStructure otherSwitchStructure = new SwitchControlStructure((SwitchStatement)other);
		return nodeSwitchStructure.match(otherSwitchStructure, this);
	}

	public boolean match(SynchronizedStatement node, Object other) {
		if (!(other instanceof SynchronizedStatement)) {
			return false;
		}
		SynchronizedStatement o = (SynchronizedStatement) other;
		if(isNestedUnderAnonymousClassDeclaration(node) && isNestedUnderAnonymousClassDeclaration(o)) {
			return super.match(node, o);
		}
		return (
				safeSubtreeMatch(node.getExpression(), o.getExpression()));
	}

	public boolean match(ThisExpression node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof ThisExpression)) {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			else {
				ThisExpression o = (ThisExpression) other;
				safeSubtreeMatch(node.getQualifier(), o.getQualifier());
			}
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(TryStatement node, Object other) {
		if (!(other instanceof TryStatement)) {
			return false;
		}
		TryStatement o = (TryStatement) other;
		if(isNestedUnderAnonymousClassDeclaration(node) && isNestedUnderAnonymousClassDeclaration(o)) {
			return super.match(node, o);
		}
		boolean resourceMatch = safeSubtreeListMatch(node.resources(), o.resources());
		boolean catchClauseMatch = safeSubtreeListMatch(node.catchClauses(), o.catchClauses());
		boolean finallyMatch;
		if(node.getFinally() == null && o.getFinally() == null)
			finallyMatch = true;
		else if(node.getFinally() != null && o.getFinally() != null)
			finallyMatch = safeSubtreeListMatch(node.getFinally().statements(), o.getFinally().statements());
		else
			finallyMatch = false;
		return resourceMatch && catchClauseMatch && finallyMatch;
	}

	public boolean match(TypeLiteral node, Object other) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(node);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression((Expression)other);
		if(isInfixExpressionWithCompositeParent((ASTNode)other)) {
			return super.match(node, other);
		}
		ASTNodeDifference astNodeDifference = new ASTNodeDifference(exp1, exp2);
		if(isTypeHolder(other)) {
			boolean typeMatch = typeBindingMatch(node.resolveTypeBinding(), getTypeBinding(other));
			if (!(other instanceof TypeLiteral)) {
				if(typeMatch) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.TYPE_COMPATIBLE_REPLACEMENT,astNodeDifference.getWeight());
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
				else {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			else {
				TypeLiteral o = (TypeLiteral) other;
				if(!node.getType().resolveBinding().isEqualTo(o.getType().resolveBinding()) || !node.getType().resolveBinding().getQualifiedName().equals(o.getType().resolveBinding().getQualifiedName())) {
					Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.LITERAL_VALUE_MISMATCH);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
				}
			}
			return typeMatch;
		}
		Difference diff = new Difference(node.toString(),other.toString(),DifferenceType.AST_TYPE_MISMATCH);
		astNodeDifference.addDifference(diff);
		addDifference(astNodeDifference);
		return false;
	}

	public boolean match(VariableDeclarationStatement node, Object other) {
		List fragments = node.fragments();
		if(fragments.size() == 1 && other instanceof ExpressionStatement) {
			VariableDeclarationFragment fragment = (VariableDeclarationFragment)fragments.get(0);
			ExpressionStatement expressionStatement = (ExpressionStatement)other;
			Expression expression = expressionStatement.getExpression();
			if(expression instanceof Assignment) {
				Assignment assignment = (Assignment)expression;
				Expression leftHandSide = assignment.getLeftHandSide();
				if(leftHandSide instanceof SimpleName) {
					SimpleName simpleName = (SimpleName)leftHandSide;
					boolean variableMatch = safeSubtreeMatch(fragment.getName(), simpleName);
					boolean variableTypeMatch = false;
					IBinding simpleNameBinding = simpleName.resolveBinding();
					IBinding fragmentNameBinding = fragment.getName().resolveBinding();
					if(simpleNameBinding.getKind() == IBinding.VARIABLE && fragmentNameBinding.getKind() == IBinding.VARIABLE) {
						IVariableBinding simpleNameVariableBinding = (IVariableBinding)simpleNameBinding;
						IVariableBinding fragmentNameVariableBinding = (IVariableBinding)fragmentNameBinding;
						variableTypeMatch = simpleNameVariableBinding.getType().isEqualTo(fragmentNameVariableBinding.getType()) &&
								simpleNameVariableBinding.getType().getQualifiedName().equals(fragmentNameVariableBinding.getType().getQualifiedName());;
					}
					boolean initializerMatch = false;
					boolean initializerTypeMatch = false;
					Expression initializer = fragment.getInitializer();
					Expression rightHandSide = assignment.getRightHandSide();
					if(initializer != null && initializer.getNodeType() == rightHandSide.getNodeType()) {
						initializerMatch = safeSubtreeMatch(initializer, rightHandSide);
						initializerTypeMatch = initializer.resolveTypeBinding().isEqualTo(rightHandSide.resolveTypeBinding()) &&
								initializer.resolveTypeBinding().getQualifiedName().equals(rightHandSide.resolveTypeBinding().getQualifiedName());
					}
					if(variableMatch && variableTypeMatch && initializerMatch && initializerTypeMatch) {
						VariableDeclaration variableDeclaration = AbstractLoopUtilities.getVariableDeclaration(simpleName);
						if(variableDeclaration != null && hasEmptyInitializer(variableDeclaration)) {
							safeSubtreeMatch(fragment.getName(), variableDeclaration.getName());
							List<ASTNode> astNodes = new ArrayList<ASTNode>();
							astNodes.add(variableDeclaration);
							ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
							reportAdditionalFragments(astNodes, this.additionallyMatchedFragments2);
							return true;
						}
					}
				}
			}
		}
		return super.match(node, other);
	}

	public boolean match(WhileStatement node, Object other) {
		if (other instanceof WhileStatement)
		{
			WhileStatement o = (WhileStatement) other;
			if(isNestedUnderAnonymousClassDeclaration(node) && isNestedUnderAnonymousClassDeclaration(o)) {
				return super.match(node, o);
			}
			if ((safeSubtreeMatch(node.getExpression(), o.getExpression())))
			{
				return true;
			}
		}
		AbstractLoop nodeConditionalLoop = new ConditionalLoop(node);
		return loopMatch(nodeConditionalLoop, other);
	}

	private boolean setterReplacedWithFieldAssignment(MethodInvocation setter, Assignment assignment) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(setter);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression(assignment);
		FieldAssignmentReplacedWithSetterInvocationDifference astNodeDifference =
				new FieldAssignmentReplacedWithSetterInvocationDifference(exp1, exp2, setter.getName().getIdentifier());
		
		Expression leftHandSide = assignment.getLeftHandSide();
		Expression rightHandSide = assignment.getRightHandSide();
		List arguments = setter.arguments();
		if(arguments.size() == 1) {
			int size = differences.size();
			boolean argumentRightHandSideMatch = safeSubtreeMatch(arguments.get(0), rightHandSide);
			for(int i=size; i<differences.size(); i++) {
				astNodeDifference.addArgumentDifference(differences.get(i));
			}
			ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
			astNodeDifference.setArgument1(new AbstractExpression((Expression)arguments.get(0)));
			ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
			astNodeDifference.setArgument2(new AbstractExpression(rightHandSide));
			if(leftHandSide instanceof FieldAccess) {
				FieldAccess fieldAccess = (FieldAccess)leftHandSide;
				SimpleName fieldAccessName = fieldAccess.getName();
				SimpleName setField = setterMethodForField(setter, fieldAccessName);
				if(setField != null && argumentRightHandSideMatch) {
					size = differences.size();
					safeSubtreeMatch(setter.getExpression(), fieldAccess.getExpression());
					for(int i=size; i<differences.size(); i++) {
						astNodeDifference.addInvokerDifference(differences.get(i));
					}
					if(setter.getExpression()==null && fieldAccess.getExpression()!=null) {
						Difference diff = new Difference("",fieldAccess.getExpression().toString(),DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
						astNodeDifference.addDifference(diff);
					}
					else if(setter.getExpression()!=null && fieldAccess.getExpression()==null) {
						Difference diff = new Difference(setter.getExpression().toString(),"",DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
						astNodeDifference.addDifference(diff);
					}
					if(setter.getExpression()!=null) {
						ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
						astNodeDifference.setInvoker1(new AbstractExpression(setter.getExpression()));
					}
					if(fieldAccess.getExpression()!=null) {
						ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
						astNodeDifference.setInvoker2(new AbstractExpression(fieldAccess.getExpression()));
					}
					PlainVariable field2 = new PlainVariable((IVariableBinding)fieldAccessName.resolveBinding());
					PlainVariable field1 = new PlainVariable((IVariableBinding)setField.resolveBinding());
					astNodeDifference.setField1(field1);
					astNodeDifference.setField2(field2);
					Difference diff = new Difference(setter.toString(),assignment.toString(),DifferenceType.FIELD_ASSIGNMENT_REPLACED_WITH_SETTER);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
					return true;
				}
			}
			else if(leftHandSide instanceof SimpleName) {
				SimpleName simpleName = (SimpleName)leftHandSide;
				SimpleName setField = setterMethodForField(setter, simpleName);
				if(setField != null && argumentRightHandSideMatch) {
					if(setter.getExpression() != null) {
						Difference diff = new Difference(setter.getExpression().toString(),"",DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
						astNodeDifference.addDifference(diff);
						ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
						astNodeDifference.setInvoker1(new AbstractExpression(setter.getExpression()));
					}
					PlainVariable field2 = new PlainVariable((IVariableBinding)simpleName.resolveBinding());
					PlainVariable field1 = new PlainVariable((IVariableBinding)setField.resolveBinding());
					astNodeDifference.setField1(field1);
					astNodeDifference.setField2(field2);
					Difference diff = new Difference(setter.toString(),assignment.toString(),DifferenceType.FIELD_ASSIGNMENT_REPLACED_WITH_SETTER);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
					return true;
				}
			}
		}
		return false;
	}
	
	private boolean fieldAssignmentReplacedWithSetter(Assignment assignment, MethodInvocation setter) {
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
		AbstractExpression exp1 = new AbstractExpression(assignment);
		ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
		AbstractExpression exp2 = new AbstractExpression(setter);
		FieldAssignmentReplacedWithSetterInvocationDifference astNodeDifference =
				new FieldAssignmentReplacedWithSetterInvocationDifference(exp1, exp2, setter.getName().getIdentifier());
		
		Expression leftHandSide = assignment.getLeftHandSide();
		Expression rightHandSide = assignment.getRightHandSide();
		List arguments = setter.arguments();
		if(arguments.size() == 1) {
			int size = differences.size();
			boolean argumentRightHandSideMatch = safeSubtreeMatch(rightHandSide, arguments.get(0));
			for(int i=size; i<differences.size(); i++) {
				astNodeDifference.addArgumentDifference(differences.get(i));
			}
			ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
			astNodeDifference.setArgument1(new AbstractExpression(rightHandSide));
			ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
			astNodeDifference.setArgument2(new AbstractExpression((Expression)arguments.get(0)));
			if(leftHandSide instanceof FieldAccess) {
				FieldAccess fieldAccess = (FieldAccess)leftHandSide;
				SimpleName fieldAccessName = fieldAccess.getName();
				SimpleName setField = setterMethodForField(setter, fieldAccessName);
				if(setField != null && argumentRightHandSideMatch) {
					size = differences.size();
					safeSubtreeMatch(fieldAccess.getExpression(), setter.getExpression());
					for(int i=size; i<differences.size(); i++) {
						astNodeDifference.addInvokerDifference(differences.get(i));
					}
					if(fieldAccess.getExpression()==null && setter.getExpression()!=null) {
						Difference diff = new Difference("",setter.getExpression().toString(),DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
						astNodeDifference.addDifference(diff);
					}
					else if(fieldAccess.getExpression()!=null && setter.getExpression()==null) {
						Difference diff = new Difference(fieldAccess.getExpression().toString(),"",DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
						astNodeDifference.addDifference(diff);
					}
					if(fieldAccess.getExpression()!=null) {
						ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
						astNodeDifference.setInvoker1(new AbstractExpression(fieldAccess.getExpression()));
					}
					if(setter.getExpression()!=null) {
						ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
						astNodeDifference.setInvoker2(new AbstractExpression(setter.getExpression()));
					}
					PlainVariable field1 = new PlainVariable((IVariableBinding)fieldAccessName.resolveBinding());
					PlainVariable field2 = new PlainVariable((IVariableBinding)setField.resolveBinding());
					astNodeDifference.setField1(field1);
					astNodeDifference.setField2(field2);
					Difference diff = new Difference(assignment.toString(),setter.toString(),DifferenceType.FIELD_ASSIGNMENT_REPLACED_WITH_SETTER);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
					return true;
				}
			}
			else if(leftHandSide instanceof SimpleName) {
				SimpleName simpleName = (SimpleName)leftHandSide;
				SimpleName setField = setterMethodForField(setter, simpleName);
				if(setField != null && argumentRightHandSideMatch) {
					if(setter.getExpression() != null) {
						Difference diff = new Difference("",setter.getExpression().toString(),DifferenceType.MISSING_METHOD_INVOCATION_EXPRESSION);
						astNodeDifference.addDifference(diff);
						ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
						astNodeDifference.setInvoker2(new AbstractExpression(setter.getExpression()));
					}
					PlainVariable field1 = new PlainVariable((IVariableBinding)simpleName.resolveBinding());
					PlainVariable field2 = new PlainVariable((IVariableBinding)setField.resolveBinding());
					astNodeDifference.setField1(field1);
					astNodeDifference.setField2(field2);
					Difference diff = new Difference(assignment.toString(),setter.toString(),DifferenceType.FIELD_ASSIGNMENT_REPLACED_WITH_SETTER);
					astNodeDifference.addDifference(diff);
					addDifference(astNodeDifference);
					return true;
				}
			}
		}
		return false;
	}

	private boolean getterMethodForField(MethodInvocation methodInvocation, SimpleName fieldName) {
		IMethodBinding methodBinding = methodInvocation.resolveMethodBinding();
		ITypeBinding declaringClassTypeBinding = methodBinding.getDeclaringClass();
		ClassObject declaringClass = ASTReader.getSystemObject().getClassObject(declaringClassTypeBinding.getQualifiedName());
		if(declaringClass != null) {
			ListIterator<MethodObject> methodIterator = declaringClass.getMethodIterator();
			while(methodIterator.hasNext()) {
				MethodObject method = methodIterator.next();
				MethodDeclaration methodDeclaration = method.getMethodDeclaration();
				if(methodDeclaration.resolveBinding().isEqualTo(methodInvocation.resolveMethodBinding())) {
					SimpleName getField = MethodDeclarationUtility.isGetter(methodDeclaration);
					if(getField != null) {
						if(getField.resolveBinding().getKind() == IBinding.VARIABLE &&
								fieldName.resolveBinding().getKind() == IBinding.VARIABLE) {
							IVariableBinding getFieldBinding = (IVariableBinding)getField.resolveBinding();
							IVariableBinding fieldNameBinding = (IVariableBinding)fieldName.resolveBinding();
							if(getFieldBinding.isEqualTo(fieldNameBinding) ||
									(getField.getIdentifier().equals(fieldName.getIdentifier()) &&
									getFieldBinding.getType().isEqualTo(fieldNameBinding.getType()) && getFieldBinding.getType().getQualifiedName().equals(fieldNameBinding.getType().getQualifiedName()))) {
								return true;
							}
						}
					}
				}
			}
		}
		return false;
	}

	private SimpleName setterMethodForField(MethodInvocation methodInvocation, SimpleName fieldName) {
		IMethodBinding methodBinding = methodInvocation.resolveMethodBinding();
		ITypeBinding declaringClassTypeBinding = methodBinding.getDeclaringClass();
		ClassObject declaringClass = ASTReader.getSystemObject().getClassObject(declaringClassTypeBinding.getQualifiedName());
		if(declaringClass != null) {
			ListIterator<MethodObject> methodIterator = declaringClass.getMethodIterator();
			while(methodIterator.hasNext()) {
				MethodObject method = methodIterator.next();
				MethodDeclaration methodDeclaration = method.getMethodDeclaration();
				if(methodDeclaration.resolveBinding().isEqualTo(methodInvocation.resolveMethodBinding())) {
					SimpleName setField = MethodDeclarationUtility.isSetter(methodDeclaration);
					if(setField != null) {
						if(setField.resolveBinding().getKind() == IBinding.VARIABLE &&
								fieldName.resolveBinding().getKind() == IBinding.VARIABLE) {
							IVariableBinding setFieldBinding = (IVariableBinding)setField.resolveBinding();
							IVariableBinding fieldNameBinding = (IVariableBinding)fieldName.resolveBinding();
							if(setFieldBinding.isEqualTo(fieldNameBinding) ||
									(setField.getIdentifier().equals(fieldName.getIdentifier()) &&
									setFieldBinding.getType().isEqualTo(fieldNameBinding.getType()) && setFieldBinding.getType().getQualifiedName().equals(fieldNameBinding.getType().getQualifiedName()))) {
								return setField;
							}
						}
					}
				}
			}
		}
		return null;
	}
	
	private boolean isNestedUnderAnonymousClassDeclaration(ASTNode node) {
		ASTNode parent = node.getParent();
		while(parent != null) {
			if(parent instanceof AnonymousClassDeclaration || parent instanceof CatchClause ||
					isFinallyBlockOfTryStatement(parent)) {
				return true;
			}
			parent = parent.getParent();
		}
		return false;
	}
	
	private boolean isFinallyBlockOfTryStatement(ASTNode node) {
		ASTNode parent = node.getParent();
		if(parent != null && parent instanceof TryStatement) {
			TryStatement tryStatement = (TryStatement)parent;
			Block finallyBlock = tryStatement.getFinally();
			if(node instanceof Block && finallyBlock != null) {
				return finallyBlock.equals((Block)node);
			}
		}
		return false;
	}

	private boolean hasEmptyInitializer(VariableDeclaration variableDeclaration) {
		if(variableDeclaration.getInitializer() == null) {
			return true;
		}
		else {
			Expression initializer = variableDeclaration.getInitializer();
			if(initializer instanceof NullLiteral) {
				return true;
			}
		}
		return false;
	}

	private boolean loopMatch(AbstractLoop nodeLoop, Object other)
	{
		AbstractLoop otherLoop = generateAbstractLoop(other);
		if (otherLoop != null)
		{
			ConditionalLoopASTNodeMatcher matcher = new ConditionalLoopASTNodeMatcher(typeRoot1, typeRoot2);
			boolean loopMatch = nodeLoop.match(otherLoop, matcher);
			if (loopMatch)
			{
				ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
				reportAdditionalFragments(nodeLoop, this.additionallyMatchedFragments1);
				if (nodeLoop instanceof ConditionalLoop && otherLoop instanceof EnhancedForLoop)
				{
					ConditionalLoop nodeConditionalLoop  = (ConditionalLoop)nodeLoop;
					EnhancedForLoop otherEnhancedForLoop = (EnhancedForLoop)otherLoop;
					SimpleName enhancedForLoopParameter  = ((EnhancedForStatement)otherLoop.getLoopStatement()).getParameter().getName();
					Collection<AbstractControlVariable> nodeConditionControlVariables = nodeConditionalLoop.getConditionControlVariables().values();
					if (!nodeConditionControlVariables.isEmpty())
					{
						ControlVariable conditionalLoopControlVariable = (ControlVariable)nodeConditionControlVariables.toArray()[0];
						SimpleName variableInitializedUsingControlVariable = AbstractLoopUtilities.getVariableInitializedUsingControlVariable(conditionalLoopControlVariable, nodeConditionalLoop.getLoopBody());
						safeSubtreeMatch(variableInitializedUsingControlVariable, enhancedForLoopParameter);
						Expression conditionalLoopDataStructureExpression = conditionalLoopControlVariable.getDataStructureExpression();
						Expression enhancedForLoopDataStructureExpression = otherEnhancedForLoop.getControlVariable().getDataStructureExpression();
						matcher.compareTypes(conditionalLoopDataStructureExpression, enhancedForLoopDataStructureExpression);
						if (conditionalLoopDataStructureExpression instanceof SimpleName)
						{
							SimpleName simpleName = (SimpleName) conditionalLoopDataStructureExpression;
							List<SimpleName> occurrencesOfSimpleName = getOccurrencesOfSimpleName(nodeConditionalLoop.getLoopBody(), simpleName);
							for (SimpleName name : occurrencesOfSimpleName)
							{
								matcher.compareTypes(name, enhancedForLoopDataStructureExpression);
							}
						}
						if (enhancedForLoopDataStructureExpression instanceof SimpleName)
						{
							SimpleName simpleName = (SimpleName) enhancedForLoopDataStructureExpression;
							List<SimpleName> occurrencesOfSimpleName = getOccurrencesOfSimpleName(otherEnhancedForLoop.getLoopBody(), simpleName);
							for (SimpleName name : occurrencesOfSimpleName)
							{
								matcher.compareTypes(conditionalLoopDataStructureExpression, name);
							}
						}
					}
				}
				if (nodeLoop instanceof ConditionalLoop && otherLoop instanceof ConditionalLoop)
				{
					ConditionalLoop nodeConditionalLoop  = (ConditionalLoop)nodeLoop;
					ConditionalLoop otherConditionalLoop  = (ConditionalLoop)otherLoop;
					Collection<AbstractControlVariable> nodeConditionControlVariables = nodeConditionalLoop.getConditionControlVariables().values();
					Collection<AbstractControlVariable> otherConditionControlVariables = otherConditionalLoop.getConditionControlVariables().values();
					if (!nodeConditionControlVariables.isEmpty() && !otherConditionControlVariables.isEmpty())
					{
						ControlVariable nodeConditionalLoopControlVariable = (ControlVariable)nodeConditionControlVariables.toArray()[0];
						ControlVariable otherConditionalLoopControlVariable = (ControlVariable)otherConditionControlVariables.toArray()[0];
						Expression nodeConditionalLoopDataStructureExpression = nodeConditionalLoopControlVariable.getDataStructureExpression();
						Expression otherConditionalLoopDataStructureExpression = otherConditionalLoopControlVariable.getDataStructureExpression();
						matcher.compareTypes(nodeConditionalLoopDataStructureExpression, otherConditionalLoopDataStructureExpression);
						if (nodeConditionalLoopDataStructureExpression instanceof SimpleName)
						{
							SimpleName simpleName = (SimpleName) nodeConditionalLoopDataStructureExpression;
							List<SimpleName> occurrencesOfSimpleName = getOccurrencesOfSimpleName(nodeConditionalLoop.getLoopBody(), simpleName);
							for (SimpleName name : occurrencesOfSimpleName)
							{
								matcher.compareTypes(name, otherConditionalLoopDataStructureExpression);
							}
						}
						if (otherConditionalLoopDataStructureExpression instanceof SimpleName)
						{
							SimpleName simpleName = (SimpleName) otherConditionalLoopDataStructureExpression;
							List<SimpleName> occurrencesOfSimpleName = getOccurrencesOfSimpleName(otherConditionalLoop.getLoopBody(), simpleName);
							for (SimpleName name : occurrencesOfSimpleName)
							{
								matcher.compareTypes(nodeConditionalLoopDataStructureExpression, name);
							}
						}
						ASTNode nodeDataStructureAccessExpression = nodeConditionalLoopControlVariable.getDataStructureAccessExpression();
						ASTNode otherDataStructureAccessExpression = otherConditionalLoopControlVariable.getDataStructureAccessExpression();
						if (nodeDataStructureAccessExpression != null && otherDataStructureAccessExpression != null)
						{
							if (nodeDataStructureAccessExpression instanceof MethodInvocation &&
									otherDataStructureAccessExpression instanceof MethodInvocation)
							{
								MethodInvocation nodeDataStructureAccessMethodInvocation = (MethodInvocation)nodeDataStructureAccessExpression;
								MethodInvocation otherDataStructureAccessMethodInvocation = (MethodInvocation)otherDataStructureAccessExpression;
								if (nodeDataStructureAccessMethodInvocation.resolveMethodBinding().isEqualTo(otherDataStructureAccessMethodInvocation.resolveMethodBinding()))
								{
									safeSubtreeListMatch(nodeDataStructureAccessMethodInvocation.arguments(), otherDataStructureAccessMethodInvocation.arguments());
								}
							}
							else
							{
								SimpleName nodeVariableInitializedUsingControlVariable = AbstractLoopUtilities.getVariableInitializedUsingControlVariable(nodeConditionalLoopControlVariable, nodeConditionalLoop.getLoopBody());
								SimpleName otherVariableInitializedUsingControlVariable = AbstractLoopUtilities.getVariableInitializedUsingControlVariable(otherConditionalLoopControlVariable, otherConditionalLoop.getLoopBody());
								safeSubtreeMatch(nodeVariableInitializedUsingControlVariable, otherVariableInitializedUsingControlVariable);
							}
						}
					}
				}
				ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
				reportAdditionalFragments(otherLoop, this.additionallyMatchedFragments2);
				if (nodeLoop instanceof EnhancedForLoop && otherLoop instanceof ConditionalLoop)
				{
					EnhancedForLoop nodeEnhancedForLoop = (EnhancedForLoop)nodeLoop;
					ConditionalLoop otherConditionalLoop = (ConditionalLoop)otherLoop;
					SimpleName enhancedForLoopParameter  = ((EnhancedForStatement)nodeLoop.getLoopStatement()).getParameter().getName();
					Collection<AbstractControlVariable> otherConditionControlVariables = otherConditionalLoop.getConditionControlVariables().values();
					if (!otherConditionControlVariables.isEmpty())
					{
						ControlVariable conditionalLoopControlVariable = (ControlVariable)otherConditionControlVariables.toArray()[0];
						SimpleName variableInitializedUsingControlVariable = AbstractLoopUtilities.getVariableInitializedUsingControlVariable(conditionalLoopControlVariable, otherConditionalLoop.getLoopBody());
						safeSubtreeMatch(enhancedForLoopParameter, variableInitializedUsingControlVariable);
						Expression enhancedForLoopDataStructureExpression = nodeEnhancedForLoop.getControlVariable().getDataStructureExpression();
						Expression conditionalLoopDataStructureExpression = conditionalLoopControlVariable.getDataStructureExpression();
						matcher.compareTypes(enhancedForLoopDataStructureExpression, conditionalLoopDataStructureExpression);
						if (enhancedForLoopDataStructureExpression instanceof SimpleName)
						{
							SimpleName simpleName = (SimpleName) enhancedForLoopDataStructureExpression;
							List<SimpleName> occurrencesOfSimpleName = getOccurrencesOfSimpleName(nodeEnhancedForLoop.getLoopBody(), simpleName);
							for (SimpleName name : occurrencesOfSimpleName)
							{
								matcher.compareTypes(name, conditionalLoopDataStructureExpression);
							}
						}
						if (conditionalLoopDataStructureExpression instanceof SimpleName)
						{
							SimpleName simpleName = (SimpleName) conditionalLoopDataStructureExpression;
							List<SimpleName> occurrencesOfSimpleName = getOccurrencesOfSimpleName(otherConditionalLoop.getLoopBody(), simpleName);
							for (SimpleName name : occurrencesOfSimpleName)
							{
								matcher.compareTypes(enhancedForLoopDataStructureExpression, name);
							}
						}
					}
				}
				for (ASTNodeDifference currentDifference : matcher.getDifferences())
				{
					addDifference(currentDifference);
				}
				return true;
			}
		}
		return false;
	}

	private static List<SimpleName> getOccurrencesOfSimpleName(ASTNode node, SimpleName simpleName)
	{
		List<SimpleName> returnList = new ArrayList<SimpleName>();
		ExpressionExtractor expressionExtractor = new ExpressionExtractor();
		List<Expression> simpleNames = new ArrayList<Expression>();
		if (node instanceof Expression)
		{
			simpleNames.addAll(expressionExtractor.getVariableInstructions((Expression)node));
		}
		else if (node instanceof Statement)
		{
			simpleNames.addAll(expressionExtractor.getVariableInstructions((Statement)node));
		}
		for (Expression currentExpression : simpleNames)
		{
			SimpleName currentSimpleName = (SimpleName)currentExpression;
			IBinding currentSimpleNameBinding = currentSimpleName.resolveBinding();
			if (currentSimpleNameBinding != null && currentSimpleNameBinding.isEqualTo(simpleName.resolveBinding()))
			{
				returnList.add(currentSimpleName);
			}
		}
		return returnList;
	}

	private static AbstractLoop generateAbstractLoop(Object object)
	{
		if (object instanceof ForStatement)
		{
			return new ConditionalLoop((ForStatement) object);
		}
		else if (object instanceof WhileStatement)
		{
			return new ConditionalLoop((WhileStatement) object);
		}
		else if (object instanceof DoStatement)
		{
			return new ConditionalLoop((DoStatement) object);
		}
		else if (object instanceof EnhancedForStatement)
		{
			return new EnhancedForLoop((EnhancedForStatement) object);
		}
		return null;
	}

	private void reportAdditionalFragments(AbstractLoop abstractLoop, List<AbstractMethodFragment> fragmentList)
	{
		List<ASTNode> additionalFragements = abstractLoop.getAdditionalFragments();
		reportAdditionalFragments(additionalFragements, fragmentList);
	}

	private boolean ifMatch(AbstractControlStructure nodeControlStructure, Object other)
	{
		AbstractControlStructure otherControlStructure = generateAbstractControlStructure(other);
		if (otherControlStructure != null)
		{
			boolean ifMatch = nodeControlStructure.match(otherControlStructure, this);
			if (ifMatch)
			{
				ASTInformationGenerator.setCurrentITypeRoot(typeRoot1);
				reportAdditionalFragments(nodeControlStructure, this.additionallyMatchedFragments1);
				ASTInformationGenerator.setCurrentITypeRoot(typeRoot2);
				reportAdditionalFragments(otherControlStructure, this.additionallyMatchedFragments2);
				return true;
			}
		}
		return false;
	}

	private static AbstractControlStructure generateAbstractControlStructure(Object object)
	{
		if (object instanceof IfStatement)
		{
			return new IfControlStructure((IfStatement) object);
		}
		else if (object instanceof SwitchStatement)
		{
			return new SwitchControlStructure((SwitchStatement) object);
		}
		else if (object instanceof ExpressionStatement)
		{
			ExpressionStatement expressionStatement = (ExpressionStatement) object;
			if (AbstractControlStructureUtilities.hasOneConditionalExpression(expressionStatement) != null)
			{
				return new TernaryControlStructure(expressionStatement);
			}
		}
		else if (object instanceof ReturnStatement)
		{
			ReturnStatement returnStatement = (ReturnStatement) object;
			if (returnStatement.getExpression() instanceof ConditionalExpression)
			{
				return new TernaryControlStructure(returnStatement);
			}
		}
		return null;
	}
	
	private void reportAdditionalFragments(AbstractControlStructure abstractControlStructure, List<AbstractMethodFragment> fragmentList)
	{
		List<ASTNode> additionalFragements = abstractControlStructure.getAdditionalFragments();
		reportAdditionalFragments(additionalFragements, fragmentList);
	}

	private void reportAdditionalFragments(List<ASTNode> additionalFragements, List<AbstractMethodFragment> fragmentList)
	{
		for(ASTNode currentFragment : additionalFragements)
		{
			ASTNode parent = currentFragment.getParent();
			if (currentFragment instanceof ExpressionStatement)
			{
				ExpressionStatement expressionStatement = (ExpressionStatement)currentFragment;
				StatementObject statementObject = new StatementObject(expressionStatement, StatementType.EXPRESSION, null);
				fragmentList.add(statementObject);
			}
			else if (currentFragment instanceof ReturnStatement)
			{
				ReturnStatement returnStatement = (ReturnStatement)currentFragment;
				StatementObject statementObject = new StatementObject(returnStatement, StatementType.RETURN, null);
				fragmentList.add(statementObject);
			}
			else if(parent instanceof ExpressionStatement)
			{
				ExpressionStatement expressionStatement = (ExpressionStatement)parent;
				StatementObject updaterStatementObject = new StatementObject(expressionStatement, StatementType.EXPRESSION, null);
				fragmentList.add(updaterStatementObject);
			}
			else if(parent instanceof VariableDeclarationStatement)
			{
				VariableDeclarationStatement variableDeclarationStatement = (VariableDeclarationStatement)parent;
				if (variableDeclarationStatement.fragments().size() == 1)
				{
					StatementObject updaterStatementObject = new StatementObject(variableDeclarationStatement, StatementType.VARIABLE_DECLARATION, null);
					fragmentList.add(updaterStatementObject);
				}
			}
			else if(parent instanceof CastExpression && parent.getParent() instanceof VariableDeclarationFragment)
			{
				List<ASTNode> nodes = new ArrayList<ASTNode>();
				nodes.add(parent.getParent());
				reportAdditionalFragments(nodes, fragmentList);
			}
			else if (currentFragment instanceof Expression)
			{
				Expression currentExpression = (Expression) currentFragment;
				AbstractExpression expressionObject = new AbstractExpression(currentExpression);
				fragmentList.add(expressionObject);
			}
		}
	}
}