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

import gr.uom.java.ast.ASTInformationGenerator;
import gr.uom.java.ast.ASTReader;
import gr.uom.java.ast.AbstractMethodDeclaration;
import gr.uom.java.ast.ClassObject;
import gr.uom.java.ast.CompilationUnitCache;
import gr.uom.java.ast.ConstructorInvocationObject;
import gr.uom.java.ast.ConstructorObject;
import gr.uom.java.ast.LibraryClassStorage;
import gr.uom.java.ast.MethodInvocationObject;
import gr.uom.java.ast.MethodObject;
import gr.uom.java.ast.ParameterObject;
import gr.uom.java.ast.SuperMethodInvocationObject;
import gr.uom.java.ast.SystemObject;
import gr.uom.java.ast.VariableDeclarationObject;
import gr.uom.java.ast.decomposition.AbstractStatement;
import gr.uom.java.ast.decomposition.MethodBodyObject;
import gr.uom.java.ast.decomposition.StatementType;
import gr.uom.java.ast.decomposition.matching.ASTNodeMatcher;
import gr.uom.java.ast.util.ExpressionExtractor;
import gr.uom.java.jdeodorant.preferences.PreferenceConstants;
import gr.uom.java.jdeodorant.refactoring.Activator;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
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.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jface.preference.IPreferenceStore;

public class MethodCallAnalyzer {
	private Set<AbstractVariable> definedVariables;
	private Set<AbstractVariable> usedVariables;
	private Set<String> thrownExceptionTypes;
	private Set<VariableDeclarationObject> variableDeclarationsInMethod;
	private int maximumCallGraphAnalysisDepth;
	
	public MethodCallAnalyzer(Set<AbstractVariable> definedVariables,
			Set<AbstractVariable> usedVariables,
			Set<String> thrownExceptionTypes,
			Set<VariableDeclarationObject> variableDeclarationsInMethod) {
		this.definedVariables = definedVariables;
		this.usedVariables = usedVariables;
		this.thrownExceptionTypes = thrownExceptionTypes;
		this.variableDeclarationsInMethod = variableDeclarationsInMethod;
		IPreferenceStore store = Activator.getDefault().getPreferenceStore();
		this.maximumCallGraphAnalysisDepth = store.getInt(PreferenceConstants.P_MAXIMUM_CALL_GRAPH_ANALYSIS_DEPTH);
	}

	public void processArgumentsOfInternalMethodInvocation(ClassObject classObject, AbstractMethodDeclaration methodObject,
			List<Expression> arguments, IMethodBinding invokedMethodBinding, AbstractVariable variable) {
		if(methodObject != null) {
			CompilationUnitCache cache = CompilationUnitCache.getInstance();
			if(cache.containsMethodExpression(methodObject)) {
				for(AbstractVariable usedField : cache.getUsedFieldsForMethodExpression(methodObject)) {
					AbstractVariable field = null;
					if(variable != null)
						field = composeVariable(variable, usedField);
					else
						field = usedField;
					usedVariables.add(field);
				}
				for(AbstractVariable definedField : cache.getDefinedFieldsForMethodExpression(methodObject)) {
					AbstractVariable field = null;
					if(variable != null)
						field = composeVariable(variable, definedField);
					else
						field = definedField;
					definedVariables.add(field);
				}
				thrownExceptionTypes.addAll(cache.getThrownExceptionTypesForMethodExpression(methodObject));
			}
			else {
				Set<AbstractVariable> usedVariablesBefore = new LinkedHashSet<AbstractVariable>(this.usedVariables);
				Set<AbstractVariable> definedVariablesBefore = new LinkedHashSet<AbstractVariable>(this.definedVariables);
				Set<String> thrownExceptionTypesBefore = new LinkedHashSet<String>(this.thrownExceptionTypes);
				processInternalMethodInvocation(classObject, methodObject, variable, new LinkedHashSet<String>());
				//save in cache
				Set<AbstractVariable> usedVariablesAfter = new LinkedHashSet<AbstractVariable>(this.usedVariables);
				usedVariablesAfter.removeAll(usedVariablesBefore);
				int usedFieldCount = 0;
				for(AbstractVariable usedField : usedVariablesAfter) {
					if(usedField instanceof PlainVariable) {
						cache.addUsedFieldForMethodExpression(usedField, methodObject);
						usedFieldCount++;
					}
					else if(usedField instanceof CompositeVariable) {
						CompositeVariable composite = (CompositeVariable)usedField;
						if(variable != null && composite.startsWithVariable(variable)) {
							//getRightPart() is not correct if variable is a CompositeVariable
							cache.addUsedFieldForMethodExpression(composite.getRightPartAfterPrefix(variable), methodObject);
							usedFieldCount++;
						}
						else {
							cache.addUsedFieldForMethodExpression(usedField, methodObject);
							usedFieldCount++;
						}
					}
				}
				if(usedFieldCount == 0) {
					cache.setEmptyUsedFieldsForMethodExpression(methodObject);
				}
				Set<AbstractVariable> definedVariablesAfter = new LinkedHashSet<AbstractVariable>(this.definedVariables);
				definedVariablesAfter.removeAll(definedVariablesBefore);
				int definedFieldCount = 0;
				for(AbstractVariable definedField : definedVariablesAfter) {
					if(definedField instanceof PlainVariable) {
						cache.addDefinedFieldForMethodExpression(definedField, methodObject);
						definedFieldCount++;
					}
					else if(definedField instanceof CompositeVariable) {
						CompositeVariable composite = (CompositeVariable)definedField;
						if(variable != null && composite.startsWithVariable(variable)) {
							//getRightPart() is not correct if variable is a CompositeVariable
							cache.addDefinedFieldForMethodExpression(composite.getRightPartAfterPrefix(variable), methodObject);
							definedFieldCount++;
						}
						else {
							cache.addDefinedFieldForMethodExpression(definedField, methodObject);
							definedFieldCount++;
						}
					}
				}
				if(definedFieldCount == 0) {
					cache.setEmptyDefinedFieldsForMethodExpression(methodObject);
				}
				LinkedHashSet<String> thrownExceptionTypesAfter = new LinkedHashSet<String>(this.thrownExceptionTypes);
				thrownExceptionTypesAfter.removeAll(thrownExceptionTypesBefore);
				cache.setThrownExceptionTypesForMethodExpression(methodObject, thrownExceptionTypesAfter);
			}
			int argumentPosition = 0;
			for(Expression argument : arguments) {
				if(argument instanceof SimpleName) {
					SimpleName argumentName = (SimpleName)argument;
					VariableDeclaration argumentDeclaration = null;
					for(VariableDeclarationObject variableDeclarationObject : variableDeclarationsInMethod) {
						VariableDeclaration variableDeclaration = variableDeclarationObject.getVariableDeclaration();
						if(variableDeclaration.resolveBinding().isEqualTo(argumentName.resolveBinding())) {
							argumentDeclaration = variableDeclaration;
							break;
						}
					}
					if(argumentDeclaration != null) {
						MethodDeclaration methodDeclaration = methodObject.getMethodDeclaration();
						String methodBindingKey = methodDeclaration.resolveBinding().getKey();
						if(cache.containsMethodArgument(methodBindingKey, argumentPosition)) {
							for(AbstractVariable usedField : cache.getUsedFieldsForMethodArgument(methodBindingKey, argumentPosition)) {
								PlainVariable argumentVariable = new PlainVariable(argumentDeclaration);
								AbstractVariable composedVariable = composeVariable(argumentVariable, usedField);
								usedVariables.add(composedVariable);
							}
							for(AbstractVariable definedField : cache.getDefinedFieldsForMethodArgument(methodBindingKey, argumentPosition)) {
								PlainVariable argumentVariable = new PlainVariable(argumentDeclaration);
								AbstractVariable composedVariable = composeVariable(argumentVariable, definedField);
								definedVariables.add(composedVariable);
							}
						}
						else {
							ParameterObject parameter = methodObject.getParameter(argumentPosition);
							//analyze only if the argument does not correspond to a varargs parameter
							if(argumentPosition < methodObject.getParameterList().size()) {
								VariableDeclaration parameterDeclaration = parameter.getSingleVariableDeclaration();
								PlainVariable argumentVariable = new PlainVariable(argumentDeclaration);
								processArgumentOfInternalMethodInvocation(methodObject, argumentVariable, argumentPosition, parameterDeclaration, new LinkedHashSet<String>());
								//save in cache
								int usedFieldCount = 0;
								for(AbstractVariable usedVariable : usedVariables) {
									if(usedVariable instanceof CompositeVariable) {
										CompositeVariable composite = (CompositeVariable)usedVariable;
										if(composite.getInitialVariable().equals(argumentVariable)) {
											cache.addUsedFieldForMethodArgument(composite.getRightPart(), methodDeclaration, argumentPosition);
											usedFieldCount++;
										}
									}
								}
								if(usedFieldCount == 0) {
									cache.setEmptyUsedFieldsForMethodArgument(methodDeclaration, argumentPosition);
								}
								int definedFieldCount = 0;
								for(AbstractVariable definedVariable : definedVariables) {
									if(definedVariable instanceof CompositeVariable) {
										CompositeVariable composite = (CompositeVariable)definedVariable;
										if(composite.getInitialVariable().equals(argumentVariable)) {
											cache.addDefinedFieldForMethodArgument(composite.getRightPart(), methodDeclaration, argumentPosition);
											definedFieldCount++;
										}
									}
								}
								if(definedFieldCount == 0) {
									cache.setEmptyDefinedFieldsForMethodArgument(methodDeclaration, argumentPosition);
								}
							}
						}
					}
				}
				argumentPosition++;
			}
		}
		else {
			LibraryClassStorage instance = LibraryClassStorage.getInstance();
			if(instance.isAnalyzed(invokedMethodBinding.getKey())) {
				handleAlreadyAnalyzedMethod(invokedMethodBinding.getKey(), variable, instance);
				int argumentPosition = 0;
				for(Expression argument : arguments) {
					if(argument instanceof SimpleName) {
						SimpleName argumentName = (SimpleName)argument;
						VariableDeclaration argumentDeclaration = null;
						for(VariableDeclarationObject variableDeclarationObject : variableDeclarationsInMethod) {
							VariableDeclaration variableDeclaration = variableDeclarationObject.getVariableDeclaration();
							if(variableDeclaration.resolveBinding().isEqualTo(argumentName.resolveBinding())) {
								argumentDeclaration = variableDeclaration;
								break;
							}
						}
						if(argumentDeclaration != null) {
							if(instance.containsMethodArgument(invokedMethodBinding.getKey(), argumentPosition)) {
								for(AbstractVariable usedField : instance.getUsedFieldsForMethodArgument(invokedMethodBinding.getKey(), argumentPosition)) {
									PlainVariable argumentVariable = new PlainVariable(argumentDeclaration);
									AbstractVariable composedVariable = composeVariable(argumentVariable, usedField);
									usedVariables.add(composedVariable);
								}
								for(AbstractVariable definedField : instance.getDefinedFieldsForMethodArgument(invokedMethodBinding.getKey(), argumentPosition)) {
									PlainVariable argumentVariable = new PlainVariable(argumentDeclaration);
									AbstractVariable composedVariable = composeVariable(argumentVariable, definedField);
									definedVariables.add(composedVariable);
								}
							}
						}
					}
					argumentPosition++;
				}
			}
			else {
				MethodDeclaration invokedMethodDeclaration = getInvokedMethodDeclaration(invokedMethodBinding);
				if(invokedMethodDeclaration != null) {
					processExternalMethodInvocation(invokedMethodDeclaration, variable, new LinkedHashSet<String>(), 0);
					int argumentPosition = 0;
					for(Expression argument : arguments) {
						if(argument instanceof SimpleName) {
							SimpleName argumentName = (SimpleName)argument;
							VariableDeclaration argumentDeclaration = null;
							for(VariableDeclarationObject variableDeclarationObject : variableDeclarationsInMethod) {
								VariableDeclaration variableDeclaration = variableDeclarationObject.getVariableDeclaration();
								if(variableDeclaration.resolveBinding().isEqualTo(argumentName.resolveBinding())) {
									argumentDeclaration = variableDeclaration;
									break;
								}
							}
							if(argumentDeclaration != null) {
								//analyze only if the argument does not correspond to a varargs parameter
								if(argumentPosition < invokedMethodDeclaration.parameters().size()) {
									VariableDeclaration parameterDeclaration = (SingleVariableDeclaration)invokedMethodDeclaration.parameters().get(argumentPosition);
									PlainVariable argumentVariable = new PlainVariable(argumentDeclaration);
									processArgumentOfExternalMethodInvocation(invokedMethodDeclaration, argumentVariable, argumentPosition, parameterDeclaration, new LinkedHashSet<String>(), 0);
									//save in cache
									int usedFieldCount = 0;
									for(AbstractVariable usedVariable : usedVariables) {
										if(usedVariable instanceof CompositeVariable) {
											CompositeVariable composite = (CompositeVariable)usedVariable;
											if(composite.getInitialVariable().equals(argumentVariable)) {
												instance.addUsedFieldForMethodArgument(composite.getRightPart(), invokedMethodDeclaration, argumentPosition);
												usedFieldCount++;
											}
										}
									}
									if(usedFieldCount == 0) {
										instance.setEmptyUsedFieldsForMethodArgument(invokedMethodDeclaration, argumentPosition);
									}
									int definedFieldCount = 0;
									for(AbstractVariable definedVariable : definedVariables) {
										if(definedVariable instanceof CompositeVariable) {
											CompositeVariable composite = (CompositeVariable)definedVariable;
											if(composite.getInitialVariable().equals(argumentVariable)) {
												instance.addDefinedFieldForMethodArgument(composite.getRightPart(), invokedMethodDeclaration, argumentPosition);
												definedFieldCount++;
											}
										}
									}
									if(definedFieldCount == 0) {
										instance.setEmptyDefinedFieldsForMethodArgument(invokedMethodDeclaration, argumentPosition);
									}
								}
							}
						}
						argumentPosition++;
					}
				}
			}
		}
	}

	private void processArgumentOfInternalMethodInvocation(AbstractMethodDeclaration methodObject, AbstractVariable argumentDeclaration, int initialArgumentPosition,
			VariableDeclaration parameterDeclaration, Set<String> processedMethods) {
		SystemObject systemObject = ASTReader.getSystemObject();
		if(methodObject.getMethodBody() == null) {
			IMethodBinding superMethodDeclarationBinding = methodObject.getMethodDeclaration().resolveBinding();
			IType superType = (IType)superMethodDeclarationBinding.getDeclaringClass().getJavaElement();
			processedMethods.add(superMethodDeclarationBinding.getKey());
			Set<IType> subTypes = CompilationUnitCache.getInstance().getSubTypes(superType);
			for(IType subType : subTypes) {
				ClassObject subClassObject = systemObject.getClassObject(subType.getFullyQualifiedName('.'));
				if(subClassObject != null) {
					ListIterator<MethodObject> methodIterator = subClassObject.getMethodIterator();
					while(methodIterator.hasNext()) {
						MethodObject subMethod = methodIterator.next();
						MethodDeclaration subMethodDeclaration = subMethod.getMethodDeclaration();
						if(equalSignature(subMethodDeclaration.resolveBinding(), superMethodDeclarationBinding)) {
							ParameterObject parameterObject = subMethod.getParameter(initialArgumentPosition);
							VariableDeclaration parameterDeclaration2 = parameterObject.getSingleVariableDeclaration();
							if(isUnprocessedMethod(processedMethods, subMethodDeclaration.resolveBinding()))
								processArgumentOfInternalMethodInvocation(subMethod, argumentDeclaration, initialArgumentPosition, parameterDeclaration2, processedMethods);
							break;
						}
					}
				}
			}
		}
		else {
			for(AbstractVariable originalField : methodObject.getDefinedFieldsThroughParameters()) {
				if(parameterDeclaration.resolveBinding().getKey().equals(originalField.getVariableBindingKey())) {
					AbstractVariable field = new CompositeVariable(argumentDeclaration, ((CompositeVariable)originalField).getRightPart());
					definedVariables.add(field);
				}
			}
			for(AbstractVariable originalField : methodObject.getUsedFieldsThroughParameters()) {
				if(parameterDeclaration.resolveBinding().getKey().equals(originalField.getVariableBindingKey())) {
					AbstractVariable field = new CompositeVariable(argumentDeclaration, ((CompositeVariable)originalField).getRightPart());
					usedVariables.add(field);
				}
			}
			processedMethods.add(methodObject.getMethodDeclaration().resolveBinding().getKey());
			Map<AbstractVariable, LinkedHashSet<MethodInvocationObject>> invokedMethodsThroughParameters = methodObject.getInvokedMethodsThroughParameters();
			for(AbstractVariable originalField : invokedMethodsThroughParameters.keySet()) {
				if(parameterDeclaration.resolveBinding().getKey().equals(originalField.getVariableBindingKey())) {
					AbstractVariable field = null;
					if(originalField instanceof PlainVariable)
						field = argumentDeclaration;
					else
						field = new CompositeVariable(argumentDeclaration, ((CompositeVariable)originalField).getRightPart());
					LinkedHashSet<MethodInvocationObject> methodInvocations = invokedMethodsThroughParameters.get(originalField);
					for(MethodInvocationObject methodInvocationObject : methodInvocations) {
						MethodInvocation methodInvocation2 = methodInvocationObject.getMethodInvocation();
						ClassObject classObject2 = systemObject.getClassObject(methodInvocationObject.getOriginClassName());
						if(classObject2 != null) {
							MethodObject methodObject2 = classObject2.getMethod(methodInvocationObject);
							if(methodObject2 != null) {
								processInternalMethodInvocation(classObject2, methodObject2, field, new LinkedHashSet<String>());
							}
						}
						else {
							LibraryClassStorage instance = LibraryClassStorage.getInstance();
							IMethodBinding invokedMethodBinding = methodInvocation2.resolveMethodBinding();
							if(instance.isAnalyzed(invokedMethodBinding.getKey())) {
								handleAlreadyAnalyzedMethod(invokedMethodBinding.getKey(), field, instance);
							}
							else {
								MethodDeclaration invokedMethodDeclaration = getInvokedMethodDeclaration(invokedMethodBinding);
								if(invokedMethodDeclaration != null)
									processExternalMethodInvocation(invokedMethodDeclaration, field, new LinkedHashSet<String>(), 0);
							}
						}
					}
				}
			}
			Map<PlainVariable, LinkedHashSet<MethodInvocationObject>> parametersPassedAsArgumentsInMethodInvocations = methodObject.getParametersPassedAsArgumentsInMethodInvocations();
			for(PlainVariable parameter : parametersPassedAsArgumentsInMethodInvocations.keySet()) {
				if(parameterDeclaration.resolveBinding().getKey().equals(parameter.getVariableBindingKey())) {
					LinkedHashSet<MethodInvocationObject> methodInvocations = parametersPassedAsArgumentsInMethodInvocations.get(parameter);
					for(MethodInvocationObject methodInvocationObject : methodInvocations) {
						ClassObject classObject2 = systemObject.getClassObject(methodInvocationObject.getOriginClassName());
						if(classObject2 != null) {
							MethodObject methodObject2 = classObject2.getMethod(methodInvocationObject);
							if(methodObject2 != null && !methodObject2.equals(methodObject)) {
								MethodInvocation methodInvocation2 = methodInvocationObject.getMethodInvocation();
								int argumentPosition = getArgumentPosition(methodInvocation2.arguments(), parameter);
								ParameterObject parameterObject = methodObject2.getParameter(argumentPosition);
								//fix for method calls with varargs
								if(parameterObject != null) {
									VariableDeclaration parameterDeclaration2 = parameterObject.getSingleVariableDeclaration();
									if(isUnprocessedMethod(processedMethods, methodInvocation2.resolveMethodBinding()))
										processArgumentOfInternalMethodInvocation(methodObject2, argumentDeclaration, argumentPosition, parameterDeclaration2, processedMethods);
								}
							}
						}
					}
				}
			}
			Map<PlainVariable, LinkedHashSet<SuperMethodInvocationObject>> parametersPassedAsArgumentsInSuperMethodInvocations = methodObject.getParametersPassedAsArgumentsInSuperMethodInvocations();
			for(PlainVariable parameter : parametersPassedAsArgumentsInSuperMethodInvocations.keySet()) {
				if(parameterDeclaration.resolveBinding().getKey().equals(parameter.getVariableBindingKey())) {
					LinkedHashSet<SuperMethodInvocationObject> superMethodInvocations = parametersPassedAsArgumentsInSuperMethodInvocations.get(parameter);
					for(SuperMethodInvocationObject superMethodInvocationObject : superMethodInvocations) {
						ClassObject classObject2 = systemObject.getClassObject(superMethodInvocationObject.getOriginClassName());
						if(classObject2 != null) {
							MethodObject methodObject2 = classObject2.getMethod(superMethodInvocationObject);
							if(methodObject2 != null) {
								SuperMethodInvocation superMethodInvocation = superMethodInvocationObject.getSuperMethodInvocation();
								int argumentPosition = getArgumentPosition(superMethodInvocation.arguments(), parameter);
								ParameterObject parameterObject = methodObject2.getParameter(argumentPosition);
								VariableDeclaration parameterDeclaration2 = parameterObject.getSingleVariableDeclaration();
								if(isUnprocessedMethod(processedMethods, superMethodInvocation.resolveMethodBinding()))
									processArgumentOfInternalMethodInvocation(methodObject2, argumentDeclaration, argumentPosition, parameterDeclaration2, processedMethods);
							}
						}
					}
				}
			}
		}
	}

	private void processArgumentOfExternalMethodInvocation(MethodDeclaration methodDeclaration, AbstractVariable argumentDeclaration, int initialArgumentPosition,
			VariableDeclaration parameterDeclaration, Set<String> processedMethods, int depth) {
		LibraryClassStorage instance = LibraryClassStorage.getInstance();
		IMethodBinding methodBinding = methodDeclaration.resolveBinding();
		Block methodBody = methodDeclaration.getBody();
		if(methodBody != null) {
			IMethod iMethod = (IMethod)methodBinding.getJavaElement();
			IClassFile iClassFile = iMethod.getClassFile();
			ASTInformationGenerator.setCurrentITypeRoot(iClassFile);
			MethodBodyObject methodBodyObject = new MethodBodyObject(methodBody);

			for(AbstractVariable originalField : methodBodyObject.getDefinedFieldsThroughParameters()) {
				if(parameterDeclaration.resolveBinding().getKey().equals(originalField.getVariableBindingKey())) {
					AbstractVariable field = new CompositeVariable(argumentDeclaration, ((CompositeVariable)originalField).getRightPart());
					definedVariables.add(field);
				}
			}
			for(AbstractVariable originalField : methodBodyObject.getUsedFieldsThroughParameters()) {
				if(parameterDeclaration.resolveBinding().getKey().equals(originalField.getVariableBindingKey())) {
					AbstractVariable field = new CompositeVariable(argumentDeclaration, ((CompositeVariable)originalField).getRightPart());
					usedVariables.add(field);
				}
			}
			processedMethods.add(methodBinding.getKey());
			Map<AbstractVariable, LinkedHashSet<MethodInvocationObject>> invokedMethodsThroughParameters = methodBodyObject.getInvokedMethodsThroughParameters();
			for(AbstractVariable originalField : invokedMethodsThroughParameters.keySet()) {
				if(parameterDeclaration.resolveBinding().getKey().equals(originalField.getVariableBindingKey())) {
					AbstractVariable field = null;
					if(originalField instanceof PlainVariable)
						field = argumentDeclaration;
					else
						field = new CompositeVariable(argumentDeclaration, ((CompositeVariable)originalField).getRightPart());
					LinkedHashSet<MethodInvocationObject> methodInvocations = invokedMethodsThroughParameters.get(originalField);
					for(MethodInvocationObject methodInvocationObject : methodInvocations) {
						MethodInvocation methodInvocation2 = methodInvocationObject.getMethodInvocation();
						IMethodBinding invokedMethodBinding = methodInvocation2.resolveMethodBinding();
						if(instance.isAnalyzed(invokedMethodBinding.getKey())) {
							handleAlreadyAnalyzedMethod(invokedMethodBinding.getKey(), field, instance);
						}
						else {
							MethodDeclaration invokedMethodDeclaration = getInvokedMethodDeclaration(invokedMethodBinding);
							if(invokedMethodDeclaration != null)
								processExternalMethodInvocation(invokedMethodDeclaration, field, new LinkedHashSet<String>(), depth);
						}
					}
				}
			}
		}
	}

	private int getArgumentPosition(List<Expression> arguments, PlainVariable argument) {
		int argumentPostion = 0;
		for(Expression arg : arguments) {
			if(arg instanceof SimpleName) {
				SimpleName argName = (SimpleName)arg;
				if(argName.resolveBinding().getKey().equals(argument.getVariableBindingKey()))
					return argumentPostion;
			}
			argumentPostion++;
		}
		return -1;
	}

	private void processInternalMethodInvocation(ClassObject classObject, AbstractMethodDeclaration methodObject, AbstractVariable variableDeclaration, Set<String> processedMethods) {
		SystemObject systemObject = ASTReader.getSystemObject();
		if(methodObject.isAbstract() || classObject.isInterface()) {
			AbstractTypeDeclaration typeDeclaration = classObject.getAbstractTypeDeclaration();
			IMethodBinding superMethodDeclarationBinding = methodObject.getMethodDeclaration().resolveBinding();
			if(isUnprocessedMethod(processedMethods, superMethodDeclarationBinding)) {
				IType superType = (IType)typeDeclaration.resolveBinding().getJavaElement();
				processedMethods.add(superMethodDeclarationBinding.getKey());
				Set<IType> subTypes = CompilationUnitCache.getInstance().getSubTypes(superType);
				Set<IType> subTypesToBeAnalyzed = new LinkedHashSet<IType>();
				if(variableDeclaration != null) {
					String initialReferenceType = variableDeclaration.getVariableType();
					for(IType subType : subTypes) {
						if(subType.getFullyQualifiedName('.').equals(initialReferenceType)) {
							subTypesToBeAnalyzed.add(subType);
							break;
						}
					}
					if(subTypesToBeAnalyzed.isEmpty())
						subTypesToBeAnalyzed.addAll(subTypes);
				}
				else
					subTypesToBeAnalyzed.addAll(subTypes);
				for(IType subType : subTypesToBeAnalyzed) {
					ClassObject subClassObject = systemObject.getClassObject(subType.getFullyQualifiedName('.'));
					if(subClassObject != null) {
						ListIterator<MethodObject> methodIterator = subClassObject.getMethodIterator();
						while(methodIterator.hasNext()) {
							MethodObject subMethod = methodIterator.next();
							if(equalSignature(subMethod.getMethodDeclaration().resolveBinding(), superMethodDeclarationBinding)) {
								processInternalMethodInvocation(subClassObject, subMethod, variableDeclaration, processedMethods);
								break;
							}
						}
					}
				}
			}
		}
		else {
			for(PlainVariable originalField : methodObject.getDefinedFieldsThroughThisReference()) {
				boolean alreadyContainsOriginalField = false;
				if(variableDeclaration != null && originalField instanceof PlainVariable) {
					if(variableDeclaration.containsPlainVariable((PlainVariable)originalField))
						alreadyContainsOriginalField = true;
				}
				if(!alreadyContainsOriginalField) {
					AbstractVariable field = null;
					if(variableDeclaration != null)
						field = composeVariable(variableDeclaration, originalField);
					else
						field = originalField;
					definedVariables.add(field);
				}
			}
			for(PlainVariable originalField : methodObject.getUsedFieldsThroughThisReference()) {
				boolean alreadyContainsOriginalField = false;
				if(variableDeclaration != null && originalField instanceof PlainVariable) {
					if(variableDeclaration.containsPlainVariable((PlainVariable)originalField))
						alreadyContainsOriginalField = true;
				}
				if(!alreadyContainsOriginalField) {
					AbstractVariable field = null;
					if(variableDeclaration != null)
						field = composeVariable(variableDeclaration, originalField);
					else
						field = originalField;
					usedVariables.add(field);
				}
			}
			thrownExceptionTypes.addAll(methodObject.getExceptionsInThrowStatements());
			thrownExceptionTypes.addAll(methodObject.getExceptionsInJavaDocThrows());
			processedMethods.add(methodObject.getMethodDeclaration().resolveBinding().getKey());
			Map<AbstractVariable, LinkedHashSet<MethodInvocationObject>> invokedMethodsThroughFields = methodObject.getInvokedMethodsThroughFields();
			for(AbstractVariable originalField : invokedMethodsThroughFields.keySet()) {
				boolean alreadyContainsOriginalField = false;
				if(variableDeclaration != null && originalField instanceof PlainVariable) {
					if(variableDeclaration.containsPlainVariable((PlainVariable)originalField))
						alreadyContainsOriginalField = true;
				}
				if(!alreadyContainsOriginalField) {
					LinkedHashSet<MethodInvocationObject> methodInvocations = invokedMethodsThroughFields.get(originalField);
					AbstractVariable field = null;
					if(variableDeclaration != null)
						field = composeVariable(variableDeclaration, originalField);
					else
						field = originalField;
					for(MethodInvocationObject methodInvocationObject : methodInvocations) {
						MethodInvocation methodInvocation2 = methodInvocationObject.getMethodInvocation();
						ClassObject classObject2 = systemObject.getClassObject(methodInvocationObject.getOriginClassName());
						if(classObject2 != null) {
							MethodObject methodObject2 = classObject2.getMethod(methodInvocationObject);
							if(methodObject2 != null) {
								if(isUnprocessedMethod(processedMethods, methodInvocation2.resolveMethodBinding()))
									processInternalMethodInvocation(classObject2, methodObject2, field, processedMethods);
							}
						}
						else {
							LibraryClassStorage instance = LibraryClassStorage.getInstance();
							IMethodBinding invokedMethodBinding = methodInvocation2.resolveMethodBinding();
							if(instance.isAnalyzed(invokedMethodBinding.getKey())) {
								handleAlreadyAnalyzedMethod(invokedMethodBinding.getKey(), field, instance);
							}
							else {
								MethodDeclaration invokedMethodDeclaration = getInvokedMethodDeclaration(invokedMethodBinding);
								if(invokedMethodDeclaration != null)
									processExternalMethodInvocation(invokedMethodDeclaration, field, new LinkedHashSet<String>(), 0);
							}
						}
					}
				}
			}
			for(MethodInvocationObject methodInvocationObject : methodObject.getInvokedMethodsThroughThisReference()) {
				MethodObject methodObject2 = classObject.getMethod(methodInvocationObject);
				if(methodObject2 != null) { 
					if(!methodObject2.equals(methodObject)) {
						MethodInvocation methodInvocation2 = methodInvocationObject.getMethodInvocation();
						if(isUnprocessedMethod(processedMethods, methodInvocation2.resolveMethodBinding()))
							processInternalMethodInvocation(classObject, methodObject2, variableDeclaration, processedMethods);
					}
				}
				else {
					//the invoked method is an inherited method
					ClassObject classObject2 = systemObject.getClassObject(methodInvocationObject.getOriginClassName());
					if(classObject2 != null) {
						methodObject2 = classObject2.getMethod(methodInvocationObject);
						if(methodObject2 != null) {
							thrownExceptionTypes.addAll(methodObject2.getExceptionsInThrowStatements());
							thrownExceptionTypes.addAll(methodObject2.getExceptionsInJavaDocThrows());
							//the commented code that follows is causing significant performance deterioration. It's time to reconsider the PDG generation strategy
							/*
