code: package gr.uom.java.ast.decomposition.matching;
  
  import gr.uom.java.ast.decomposition.AbstractExpression;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import org.eclipse.jdt.core.dom.ASTNode;
  import org.eclipse.jdt.core.dom.Assignment;
  import org.eclipse.jdt.core.dom.Block;
  import org.eclipse.jdt.core.dom.Expression;
  import org.eclipse.jdt.core.dom.FieldAccess;
  import org.eclipse.jdt.core.dom.IBinding;
  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.MethodDeclaration;
  import org.eclipse.jdt.core.dom.QualifiedName;
  import org.eclipse.jdt.core.dom.SimpleName;
  import org.eclipse.jdt.core.dom.TryStatement;
  import org.eclipse.jdt.core.dom.Type;
  
  public class ASTNodeDifference {
  	private AbstractExpression expression1;
  	private AbstractExpression expression2;
  	private List<Difference> differences;
  	private BindingSignaturePair bindingSignaturePair;
  	
  	public ASTNodeDifference(AbstractExpression e1, AbstractExpression e2) {
  		this.expression1=e1;
  		this.expression2=e2;
  		this.bindingSignaturePair = new BindingSignaturePair(e1, e2);
  		this.differences = new ArrayList<Difference>();
  	}
  
  	public List<Difference> getDifferences() {
  		return differences;
  	}
  
  	public AbstractExpression getExpression1() {
  		return expression1;
  	}
  
  	public AbstractExpression getExpression2() {
  		return expression2;
  	}
  
  	public BindingSignaturePair getBindingSignaturePair() {
  		return bindingSignaturePair;
  	}
  
  	public void addDifference(Difference diff)
  	{
  		differences.add(diff);
  	}
  
  	public boolean containsDifferenceType(DifferenceType type) {
  		for(Difference difference : differences)
  		{
  			if(difference.getType().equals(type))
  				return true;
  		}
  		return false;
  	}
  
  	public boolean containsOnlyDifferenceType(DifferenceType type) {
  		for(Difference difference : differences)
  		{
  			if(!difference.getType().equals(type))
  				return false;
  		}
  		if(!differences.isEmpty())
  			return true;
  		return false;
  	}
  
  	public boolean isParameterizable()
  	{
  		for(Difference difference : differences)
  		{
  			if(typeMismatch(difference))
  				return false;
  		}
  		return true;
  	}
  	
  	private boolean typeMismatch(Difference diff)
  	{
  		if(diff.getType().equals(DifferenceType.AST_TYPE_MISMATCH)
  			|| (diff.getType().equals(DifferenceType.VARIABLE_TYPE_MISMATCH) && isVariableTypeMismatch())
  			|| (diff.getType().equals(DifferenceType.OPERATOR_MISMATCH) && isExpressionOfIfStatementNestedAtLevelZero())
  			|| diff.getType().equals(DifferenceType.ANONYMOUS_CLASS_DECLARATION_MISMATCH))
  					return true;
  		return false;
  	}
  
  	private boolean isVariableTypeMismatch() {
  		if(isQualifierOfQualifiedName() || isExpressionOfFieldAccess())
  			return false;
  		return true;
  	}
  
  	private boolean isExpressionOfIfStatementNestedAtLevelZero() {
  		if(expression1.getExpression().getParent() instanceof IfStatement &&
  				expression2.getExpression().getParent() instanceof IfStatement) {
  			IfStatement if1 = (IfStatement)expression1.getExpression().getParent();
  			IfStatement if2 = (IfStatement)expression2.getExpression().getParent();
  			boolean noElsePart = if1.getElseStatement() == null && if2.getElseStatement() == null;
  			ASTNode parent1 = if1.getParent();
  			while(parent1 instanceof Block) {
  				parent1 = parent1.getParent();
  			}
  			ASTNode parent2 = if2.getParent();
  			while(parent2 instanceof Block) {
  				parent2 = parent2.getParent();
  			}
  			if(parent1 instanceof MethodDeclaration && parent2 instanceof MethodDeclaration) {
  				return noElsePart;
  			}
  			if(parent1 instanceof TryStatement && parent2 instanceof TryStatement) {
  				TryStatement try1 = (TryStatement)parent1;
  				TryStatement try2 = (TryStatement)parent2;
  				parent1 = try1.getParent();
  				while(parent1 instanceof Block) {
  					parent1 = parent1.getParent();
  				}
  				parent2 = try2.getParent();
  				while(parent2 instanceof Block) {
  					parent2 = parent2.getParent();
  				}
  				if(parent1 instanceof MethodDeclaration && parent2 instanceof MethodDeclaration) {
  					return noElsePart;
  				}
  			}
  		}
  		return false;
  	}
  
  	private boolean isQualifierOfQualifiedName() {
  		Expression exp1 = expression1.getExpression();
  		Expression exp2 = expression2.getExpression();
  		ASTNode node1 = exp1.getParent();
  		ASTNode node2 = exp2.getParent();
  		if(node1 instanceof QualifiedName && node2 instanceof QualifiedName) {
  			QualifiedName qual1 = (QualifiedName)node1;
  			QualifiedName qual2 = (QualifiedName)node2;
  			if(qual1.getQualifier().equals(exp1) && qual2.getQualifier().equals(exp2))
  				return true;
  		}
  		return false;
  	}
  
  	private boolean isExpressionOfFieldAccess() {
  		Expression exp1 = expression1.getExpression();
  		Expression exp2 = expression2.getExpression();
  		ASTNode node1 = exp1.getParent();
  		ASTNode node2 = exp2.getParent();
  		if(node1 instanceof FieldAccess && node2 instanceof FieldAccess) {
  			FieldAccess fieldAccess1 = (FieldAccess)node1;
  			FieldAccess fieldAccess2 = (FieldAccess)node2;
  			if(fieldAccess1.getExpression().equals(exp1) && fieldAccess2.getExpression().equals(exp2))
  				return true;
  		}
  		return false;
  	}
  
  	public boolean isEmpty() {
  		return differences.isEmpty();
  	}
  	
  	public boolean isParentNodeDifferenceOf(ASTNodeDifference nodeDifference) {
  		Expression thisExpression1 = expression1.getExpression();
  		Expression thisExpression2 = expression2.getExpression();
  		
  		thisExpression1 = getParentExpressionOfMethodNameOrTypeName(thisExpression1);
  		thisExpression2 = getParentExpressionOfMethodNameOrTypeName(thisExpression2);
  		
  		Expression otherExpression1 = nodeDifference.expression1.getExpression();
  		Expression otherExpression2 = nodeDifference.expression2.getExpression();
  		
  		if(isParent(thisExpression1, otherExpression1) && isParent(thisExpression2, otherExpression2))
  			return true;
  		return false;
  	}
  	
  	private boolean isParent(Expression parent, ASTNode child) {
  		if(child.getParent().equals(parent))
  			return true;
  		else if(child.getParent() instanceof Expression) {
  			return isParent(parent, (Expression)child.getParent());
  		}
  		else if(child.getParent() instanceof Type) {
  			return isParent(parent, (Type)child.getParent());
  		}
  		else {
  			return false;
  		}
  	}
  
  	public boolean isLeftHandSideOfAssignment() {
  		Expression exp1 = expression1.getExpression();
  		Expression exp2 = expression2.getExpression();
  		ASTNode node1 = exp1.getParent();
  		ASTNode node2 = exp2.getParent();
  		if(node1 instanceof Assignment && node2 instanceof Assignment) {
  			Assignment assignment1 = (Assignment)node1;
  			Assignment assignment2 = (Assignment)node2;
  			if(assignment1.getLeftHandSide().equals(exp1) && assignment2.getLeftHandSide().equals(exp2)) {
  				return true;
  			}
  		}
  		return false;
  	}
  
  	public static Expression getParentExpressionOfMethodNameOrTypeName(Expression expression) {
  		if(expression instanceof SimpleName) {
  			SimpleName simpleName = (SimpleName)expression;
  			IBinding binding = simpleName.resolveBinding();
  			if(binding != null) {
  				if(binding.getKind() == IBinding.METHOD) {
  					if(expression.getParent() instanceof Expression) {
  						return (Expression)expression.getParent();
  					}
  				}
  				if(binding.getKind() == IBinding.TYPE) {
  					if(expression.getParent() instanceof Type) {
  						Type type = (Type)expression.getParent();
  						if(type.getParent() instanceof Expression) {
  							return (Expression)type.getParent();
  						}
  					}
  				}
  				if(binding.getKind() == ITypeBinding.VARIABLE) {
  					if(expression.getParent() instanceof QualifiedName) {
  						QualifiedName fieldAccess = (QualifiedName)expression.getParent();
  						SimpleName fieldName = fieldAccess.getName();
  						IBinding fieldNameBinding = fieldName.resolveBinding();
  						if(fieldNameBinding.getKind() == IBinding.VARIABLE) {
  							IVariableBinding fieldNameVariableBinding = (IVariableBinding)fieldNameBinding;
  							if(fieldAccess.getQualifier().equals(expression) && fieldNameVariableBinding.isField()) {
  								return fieldAccess;
  							}
  						}
  					}
  				}
  			}
  		}
  		else if(expression instanceof QualifiedName) {
  			QualifiedName qualifiedName = (QualifiedName)expression;
  			IBinding binding = qualifiedName.resolveBinding();
  			if(binding != null) {
  				if(binding.getKind() == IBinding.TYPE) {
  					if(qualifiedName.getParent() instanceof Type) {
  						Type type = (Type)qualifiedName.getParent();
  						if(type.getParent() instanceof Expression) {
  							return (Expression)type.getParent();
  						}
  					}
  				}
  			}
  		}
  		return expression;
  	}
  
  	public int getWeight() {
  		return bindingSignaturePair.getWeight();
  	}
  
  	@Override
  	public int hashCode() {
  		final int prime = 31;
  		int result = 1;
  		result = prime * result
  				+ ((differences == null) ? 0 : differences.hashCode());
  		result = prime * result
  				+ ((expression1 == null) ? 0 : expression1.hashCode());
  		result = prime * result
  				+ ((expression2 == null) ? 0 : expression2.hashCode());
  		return result;
  	}
  
  	@Override
  	public boolean equals(Object obj) {
  		if (this == obj)
  			return true;
  		if (obj == null)
  			return false;
  		if (getClass() != obj.getClass())
  			return false;
  		ASTNodeDifference other = (ASTNodeDifference) obj;
  		if (differences == null) {
  			if (other.differences != null)
  				return false;
  		} else if (!differences.equals(other.differences))
  			return false;
  		if (expression1 == null) {
  			if (other.expression1 != null)
  				return false;
  		} else if (!expression1.equals(other.expression1))
  			return false;
  		if (expression2 == null) {
  			if (other.expression2 != null)
  				return false;
  		} else if (!expression2.equals(other.expression2))
  			return false;
  		return true;
  	}
  
  	public String toString()
  	{
  		StringBuilder sb = new StringBuilder();
  		for(Difference difference : differences)
  		{
  			sb.append(difference.toString()).append("\n");
  		}
  		return sb.toString();
  	}
  }
  