/*******************************************************************************
 * Copyright (c) 2008, 2017 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.aspectj.org.eclipse.jdt.internal.compiler;

import java.util.ArrayList;
import java.util.Map;

import org.aspectj.org.eclipse.jdt.core.compiler.CharOperation;
import org.aspectj.org.eclipse.jdt.internal.compiler.ISourceElementRequestor.ParameterInfo;
import org.aspectj.org.eclipse.jdt.internal.compiler.ISourceElementRequestor.TypeParameterInfo;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.AnnotationMethodDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.Argument;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ArrayAllocationExpression;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ArrayReference;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.Assignment;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ExportsStatement;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.Expression;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.Initializer;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ModuleDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.OpensStatement;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.QualifiedAllocationExpression;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ThisReference;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.TypeParameter;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.aspectj.org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.aspectj.org.eclipse.jdt.internal.compiler.util.HashtableOfObjectToInt;

@SuppressWarnings({"rawtypes", "unchecked"})
public class SourceElementNotifier {
	/**
	 * An ast visitor that visits local type declarations.
	 */
	public class LocalDeclarationVisitor extends ASTVisitor {
		public ImportReference currentPackage;
		ArrayList declaringTypes;
		public void pushDeclaringType(TypeDeclaration declaringType) {
			if (this.declaringTypes == null) {
				this.declaringTypes = new ArrayList();
			}
			this.declaringTypes.add(declaringType);
		}
		public void popDeclaringType() {
			this.declaringTypes.remove(this.declaringTypes.size()-1);
		}
		public TypeDeclaration peekDeclaringType() {
			if (this.declaringTypes == null) return null;
			int size = this.declaringTypes.size();
			if (size == 0) return null;
			return (TypeDeclaration) this.declaringTypes.get(size-1);
		}
		@Override
		public boolean visit(TypeDeclaration typeDeclaration, BlockScope scope) {
			notifySourceElementRequestor(typeDeclaration, true, peekDeclaringType(), this.currentPackage);
			return false; // don't visit members as this was done during notifySourceElementRequestor(...)
		}
		@Override
		public boolean visit(TypeDeclaration typeDeclaration, ClassScope scope) {
			notifySourceElementRequestor(typeDeclaration, true, peekDeclaringType(), this.currentPackage);
			return false; // don't visit members as this was done during notifySourceElementRequestor(...)
		}
	}

	ISourceElementRequestor requestor;
	boolean reportReferenceInfo;
	char[][] typeNames;
	char[][] superTypeNames;
	int nestedTypeIndex;
	LocalDeclarationVisitor localDeclarationVisitor = null;

	HashtableOfObjectToInt sourceEnds;
	Map<ASTNode,char[][]> nodesToCategories;

	int initialPosition;
	int eofPosition;

public SourceElementNotifier(ISourceElementRequestor requestor, boolean reportLocalDeclarations) {
	this.requestor = requestor;
	if (reportLocalDeclarations) {
		this.localDeclarationVisitor = new LocalDeclarationVisitor();
	}
	this.typeNames = new char[4][];
	this.superTypeNames = new char[4][];
	this.nestedTypeIndex = 0;
}
protected Object[][] getArgumentInfos(Argument[] arguments) {
	int argumentLength = arguments.length;
	char[][] argumentTypes = new char[argumentLength][];
	char[][] argumentNames = new char[argumentLength][];
	ParameterInfo[] parameterInfos = new ParameterInfo[argumentLength];
	for (int i = 0; i < argumentLength; i++) {
		Argument argument = arguments[i];
		argumentTypes[i] = CharOperation.concatWith(argument.type.getParameterizedTypeName(), '.');
		char[] name = argument.name;
		argumentNames[i] = name;
		ParameterInfo parameterInfo = new ParameterInfo();
		parameterInfo.declarationStart = argument.declarationSourceStart;
		parameterInfo.declarationEnd = argument.declarationSourceEnd;
		parameterInfo.nameSourceStart = argument.sourceStart;
		parameterInfo.nameSourceEnd = argument.sourceEnd;
		parameterInfo.modifiers = argument.modifiers;
		parameterInfo.name = name;
		parameterInfos[i] = parameterInfo;
	}

	return new Object[][] { parameterInfos, new char[][][] { argumentTypes, argumentNames } };
}
protected char[][] getInterfaceNames(TypeDeclaration typeDeclaration) {
	char[][] interfaceNames = null;
	int superInterfacesLength = 0;
	TypeReference[] superInterfaces = typeDeclaration.superInterfaces;
	if (superInterfaces != null) {
		superInterfacesLength = superInterfaces.length;
		interfaceNames = new char[superInterfacesLength][];
	} else {
		if ((typeDeclaration.bits & ASTNode.IsAnonymousType) != 0) {
			// see PR 3442
			QualifiedAllocationExpression alloc = typeDeclaration.allocation;
			if (alloc != null && alloc.type != null) {
				superInterfaces = new TypeReference[] { alloc.type};
				superInterfacesLength = 1;
				interfaceNames = new char[1][];
			}
		}
	}
	if (superInterfaces != null) {
		for (int i = 0; i < superInterfacesLength; i++) {
			interfaceNames[i] =
				CharOperation.concatWith(superInterfaces[i].getParameterizedTypeName(), '.');
		}
	}
	return interfaceNames;
}
protected char[] getSuperclassName(TypeDeclaration typeDeclaration) {
	TypeReference superclass = typeDeclaration.superclass;
	return superclass != null ? CharOperation.concatWith(superclass.getParameterizedTypeName(), '.') : null;
}
protected char[][] getThrownExceptions(AbstractMethodDeclaration methodDeclaration) {
	char[][] thrownExceptionTypes = null;
	TypeReference[] thrownExceptions = methodDeclaration.thrownExceptions;
	if (thrownExceptions != null) {
		int thrownExceptionLength = thrownExceptions.length;
		thrownExceptionTypes = new char[thrownExceptionLength][];
		for (int i = 0; i < thrownExceptionLength; i++) {
			thrownExceptionTypes[i] =
				CharOperation.concatWith(thrownExceptions[i].getParameterizedTypeName(), '.');
		}
	}
	return thrownExceptionTypes;
}
protected char[][] getTypeParameterBounds(TypeParameter typeParameter) {
	TypeReference firstBound = typeParameter.type;
	TypeReference[] otherBounds = typeParameter.bounds;
	char[][] typeParameterBounds = null;
	if (firstBound != null) {
		if (otherBounds != null) {
			int otherBoundsLength = otherBounds.length;
			char[][] boundNames = new char[otherBoundsLength+1][];
			boundNames[0] = CharOperation.concatWith(firstBound.getParameterizedTypeName(), '.');
			for (int j = 0; j < otherBoundsLength; j++) {
				boundNames[j+1] =
					CharOperation.concatWith(otherBounds[j].getParameterizedTypeName(), '.');
			}
			typeParameterBounds = boundNames;
		} else {
			typeParameterBounds = new char[][] { CharOperation.concatWith(firstBound.getParameterizedTypeName(), '.')};
		}
	} else {
		typeParameterBounds = CharOperation.NO_CHAR_CHAR;
	}

	return typeParameterBounds;
}
private TypeParameterInfo[] getTypeParameterInfos(TypeParameter[] typeParameters) {
	if (typeParameters == null) return null;
	int typeParametersLength = typeParameters.length;
	TypeParameterInfo[] result = new TypeParameterInfo[typeParametersLength];
	for (int i = 0; i < typeParametersLength; i++) {
		TypeParameter typeParameter = typeParameters[i];
		char[][] typeParameterBounds = getTypeParameterBounds(typeParameter);
		ISourceElementRequestor.TypeParameterInfo typeParameterInfo = new ISourceElementRequestor.TypeParameterInfo();
		typeParameterInfo.typeAnnotated = ((typeParameter.bits & ASTNode.HasTypeAnnotations) != 0);
		typeParameterInfo.declarationStart = typeParameter.declarationSourceStart;
		typeParameterInfo.declarationEnd = typeParameter.declarationSourceEnd;
		typeParameterInfo.name = typeParameter.name;
		typeParameterInfo.nameSourceStart = typeParameter.sourceStart;
		typeParameterInfo.nameSourceEnd = typeParameter.sourceEnd;
		typeParameterInfo.bounds = typeParameterBounds;
		result[i] = typeParameterInfo;
	}
	return result;
}
/*
 * Checks whether one of the annotations is the @Deprecated annotation
 * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=89807)
 */
private boolean hasDeprecatedAnnotation(Annotation[] annotations) {
	if (annotations != null) {
		for (int i = 0, length = annotations.length; i < length; i++) {
			Annotation annotation = annotations[i];
			if (CharOperation.equals(annotation.type.getLastToken(), TypeConstants.JAVA_LANG_DEPRECATED[2])) {
				return true;
			}
		}
	}
	return false;
}
/*
 * Update the bodyStart of the corresponding parse node
 */
protected void notifySourceElementRequestor(AbstractMethodDeclaration methodDeclaration, TypeDeclaration declaringType, ImportReference currentPackage) {

	// range check
	boolean isInRange =
				this.initialPosition <= methodDeclaration.declarationSourceStart
				&& this.eofPosition >= methodDeclaration.declarationSourceEnd;

	if (methodDeclaration.isClinit()) {
		this.visitIfNeeded(methodDeclaration);
		return;
	}

	if (methodDeclaration.isDefaultConstructor()) {
		if (this.reportReferenceInfo) {
			ConstructorDeclaration constructorDeclaration = (ConstructorDeclaration) methodDeclaration;
			ExplicitConstructorCall constructorCall = constructorDeclaration.constructorCall;
			if (constructorCall != null) {
				switch(constructorCall.accessMode) {
					case ExplicitConstructorCall.This :
						this.requestor.acceptConstructorReference(
							this.typeNames[this.nestedTypeIndex-1],
							constructorCall.arguments == null ? 0 : constructorCall.arguments.length,
							constructorCall.sourceStart);
						break;
					case ExplicitConstructorCall.Super :
					case ExplicitConstructorCall.ImplicitSuper :
						this.requestor.acceptConstructorReference(
							this.superTypeNames[this.nestedTypeIndex-1],
							constructorCall.arguments == null ? 0 : constructorCall.arguments.length,
							constructorCall.sourceStart);
						break;
				}
			}
		}
		return;
	}
	char[][] argumentTypes = null;
	char[][] argumentNames = null;
	boolean isVarArgs = false;
	Argument[] arguments = methodDeclaration.arguments;
	ParameterInfo[] parameterInfos = null; 
	ISourceElementRequestor.MethodInfo methodInfo = new ISourceElementRequestor.MethodInfo();
	methodInfo.typeAnnotated = ((methodDeclaration.bits & ASTNode.HasTypeAnnotations) != 0);

	if (arguments != null) {
		Object[][] argumentInfos = getArgumentInfos(arguments);
		parameterInfos = (ParameterInfo[]) argumentInfos[0];
		argumentTypes = (char[][]) argumentInfos[1][0];
		argumentNames = (char[][]) argumentInfos[1][1];

		isVarArgs = arguments[arguments.length-1].isVarArgs();
	}
	char[][] thrownExceptionTypes = getThrownExceptions(methodDeclaration);
	// by default no selector end position
	int selectorSourceEnd = -1;
	if (methodDeclaration.isConstructor()) {
		selectorSourceEnd = this.sourceEnds.get(methodDeclaration);
		if (isInRange){
			int currentModifiers = methodDeclaration.modifiers;
			currentModifiers &= ExtraCompilerModifiers.AccJustFlag | ClassFileConstants.AccDeprecated;
			if (isVarArgs)
				currentModifiers |= ClassFileConstants.AccVarargs;
			if (hasDeprecatedAnnotation(methodDeclaration.annotations))
				currentModifiers |= ClassFileConstants.AccDeprecated;

			methodInfo.isConstructor = true;
			methodInfo.declarationStart = methodDeclaration.declarationSourceStart;
			methodInfo.modifiers = currentModifiers;
			methodInfo.name = methodDeclaration.selector;
			methodInfo.nameSourceStart = methodDeclaration.sourceStart;
			methodInfo.nameSourceEnd = selectorSourceEnd;
			methodInfo.parameterTypes = argumentTypes;
			methodInfo.parameterNames = argumentNames;
			methodInfo.exceptionTypes = thrownExceptionTypes;
			methodInfo.typeParameters = getTypeParameterInfos(methodDeclaration.typeParameters());
			methodInfo.parameterInfos = parameterInfos;
			methodInfo.categories = this.nodesToCategories.get(methodDeclaration);
			methodInfo.annotations = methodDeclaration.annotations;
			methodInfo.declaringPackageName = currentPackage == null ? CharOperation.NO_CHAR : CharOperation.concatWith(currentPackage.tokens, '.');
			methodInfo.declaringTypeModifiers = declaringType.modifiers;
			methodInfo.extraFlags = ExtraFlags.getExtraFlags(declaringType);
			methodInfo.node = methodDeclaration;
			this.requestor.enterConstructor(methodInfo);
		}
		if (this.reportReferenceInfo) {
			ConstructorDeclaration constructorDeclaration = (ConstructorDeclaration) methodDeclaration;
			ExplicitConstructorCall constructorCall = constructorDeclaration.constructorCall;
			if (constructorCall != null) {
				switch(constructorCall.accessMode) {
					case ExplicitConstructorCall.This :
						this.requestor.acceptConstructorReference(
							this.typeNames[this.nestedTypeIndex-1],
							constructorCall.arguments == null ? 0 : constructorCall.arguments.length,
							constructorCall.sourceStart);
						break;
					case ExplicitConstructorCall.Super :
					case ExplicitConstructorCall.ImplicitSuper :
						this.requestor.acceptConstructorReference(
							this.superTypeNames[this.nestedTypeIndex-1],
							constructorCall.arguments == null ? 0 : constructorCall.arguments.length,
							constructorCall.sourceStart);
						break;
				}
			}
		}
		this.visitIfNeeded(methodDeclaration);
		if (isInRange){
			this.requestor.exitConstructor(methodDeclaration.declarationSourceEnd);
		}
		return;
	}
	selectorSourceEnd = this.sourceEnds.get(methodDeclaration);
	if (isInRange) {
		int currentModifiers = methodDeclaration.modifiers;
		currentModifiers &= ExtraCompilerModifiers.AccJustFlag | ClassFileConstants.AccDeprecated | ClassFileConstants.AccAnnotationDefault | ExtraCompilerModifiers.AccDefaultMethod;
		if (isVarArgs)
			currentModifiers |= ClassFileConstants.AccVarargs;
		if (hasDeprecatedAnnotation(methodDeclaration.annotations))
			currentModifiers |= ClassFileConstants.AccDeprecated;

		TypeReference returnType = methodDeclaration instanceof MethodDeclaration
			? ((MethodDeclaration) methodDeclaration).returnType
			: null;
		methodInfo.isAnnotation = methodDeclaration instanceof AnnotationMethodDeclaration;
		methodInfo.declarationStart = methodDeclaration.declarationSourceStart;
		methodInfo.modifiers = currentModifiers;
		methodInfo.returnType = returnType == null ? null : CharOperation.concatWith(returnType.getParameterizedTypeName(), '.');
		methodInfo.name = methodDeclaration.selector;
		methodInfo.nameSourceStart = methodDeclaration.sourceStart;
		methodInfo.nameSourceEnd = selectorSourceEnd;
		methodInfo.parameterTypes = argumentTypes;
		methodInfo.parameterNames = argumentNames;
		methodInfo.exceptionTypes = thrownExceptionTypes;
		methodInfo.typeParameters = getTypeParameterInfos(methodDeclaration.typeParameters());
		methodInfo.parameterInfos = parameterInfos;
		methodInfo.categories = this.nodesToCategories.get(methodDeclaration);
		methodInfo.annotations = methodDeclaration.annotations;
		methodInfo.node = methodDeclaration;
		methodInfo.enclosingType = declaringType;
		methodInfo.declaringPackageName = currentPackage == null ? CharOperation.NO_CHAR : CharOperation.concatWith(currentPackage.tokens, '.');
		this.requestor.enterMethod(methodInfo);
	}

	this.visitIfNeeded(methodDeclaration);

	if (isInRange) {
		if (methodDeclaration instanceof AnnotationMethodDeclaration) {
			AnnotationMethodDeclaration annotationMethodDeclaration = (AnnotationMethodDeclaration) methodDeclaration;
			Expression expression = annotationMethodDeclaration.defaultValue;
			if (expression != null) {
				this.requestor.exitMethod(methodDeclaration.declarationSourceEnd, expression);
				return;
			}
		}
		this.requestor.exitMethod(methodDeclaration.declarationSourceEnd, null);
	}
}

/*
 * Update the bodyStart of the corresponding parse node
 */
public void notifySourceElementRequestor(
		CompilationUnitDeclaration parsedUnit,
		int sourceStart,
		int sourceEnd,
		boolean reportReference,
		HashtableOfObjectToInt sourceEndsMap,
		Map nodesToCategoriesMap) {

	this.initialPosition = sourceStart;
	this.eofPosition = sourceEnd;

	this.reportReferenceInfo = reportReference;
	this.sourceEnds = sourceEndsMap;
	this.nodesToCategories = nodesToCategoriesMap;

	try {
		// range check
		boolean isInRange =
					this.initialPosition <= parsedUnit.sourceStart
					&& this.eofPosition >= parsedUnit.sourceEnd;

		// collect the top level ast nodes
		int length = 0;
		ASTNode[] nodes = null;
		if (isInRange) {
			this.requestor.enterCompilationUnit();
		}
		ImportReference currentPackage = parsedUnit.currentPackage;
		if (this.localDeclarationVisitor !=  null) {
			this.localDeclarationVisitor.currentPackage = currentPackage;
		}
		ImportReference[] imports = parsedUnit.imports;
		TypeDeclaration[] types = parsedUnit.types;
		length =
			(currentPackage == null ? 0 : 1)
			+ (imports == null ? 0 : imports.length)
			+ (types == null ? 0 : types.length)
			+ (parsedUnit.moduleDeclaration == null ? 0 : 1);
		nodes = new ASTNode[length];
		int index = 0;
		if (currentPackage != null) {
			nodes[index++] = currentPackage;
		}
		if (imports != null) {
			for (int i = 0, max = imports.length; i < max; i++) {
				nodes[index++] = imports[i];
			}
		}
		if (types != null) {
			for (int i = 0, max = types.length; i < max; i++) {
				nodes[index++] = types[i];
			}
		}
		
		if (parsedUnit.moduleDeclaration != null)
			nodes[index++] = parsedUnit.moduleDeclaration;

		// notify the nodes in the syntactical order
		if (length > 0) {
			quickSort(nodes, 0, length-1);
			for (int i=0;i<length;i++) {
				ASTNode node = nodes[i];
				if (node instanceof ImportReference) {
					ImportReference importRef = (ImportReference)node;
					if (node == parsedUnit.currentPackage) {
						notifySourceElementRequestor(importRef, true);
					} else {
						notifySourceElementRequestor(importRef, false);
					}
				} else if (node instanceof TypeDeclaration) {
					notifySourceElementRequestor((TypeDeclaration)node, true, null, currentPackage);
				} else if (node instanceof ModuleDeclaration) {
					notifySourceElementRequestor(parsedUnit.moduleDeclaration);
				}
			}
		}

		if (isInRange) {
			this.requestor.exitCompilationUnit(parsedUnit.sourceEnd);
		}
	} finally {
		reset();
	}
}

/*
* Update the bodyStart of the corresponding parse node
*/
protected void notifySourceElementRequestor(FieldDeclaration fieldDeclaration, TypeDeclaration declaringType) {

	// range check
	boolean isInRange =
				this.initialPosition <= fieldDeclaration.declarationSourceStart
				&& this.eofPosition >= fieldDeclaration.declarationSourceEnd;

	switch(fieldDeclaration.getKind()) {
		case AbstractVariableDeclaration.ENUM_CONSTANT:
			if (this.reportReferenceInfo) {
				// accept constructor reference for enum constant
				if (fieldDeclaration.initialization instanceof AllocationExpression) {
					AllocationExpression alloc = (AllocationExpression) fieldDeclaration.initialization;
					this.requestor.acceptConstructorReference(
						declaringType.name,
						alloc.arguments == null ? 0 : alloc.arguments.length,
						alloc.sourceStart);
				}
			}
			// $FALL-THROUGH$
		case AbstractVariableDeclaration.FIELD:
			int fieldEndPosition = this.sourceEnds.get(fieldDeclaration);
			if (fieldEndPosition == -1) {
				// use the declaration source end by default
				fieldEndPosition = fieldDeclaration.declarationSourceEnd;
			}
			if (isInRange) {
				int currentModifiers = fieldDeclaration.modifiers;

				// remember deprecation so as to not lose it below
				boolean deprecated = (currentModifiers & ClassFileConstants.AccDeprecated) != 0 || hasDeprecatedAnnotation(fieldDeclaration.annotations);

				char[] typeName = null;
				if (fieldDeclaration.type == null) {
					// enum constant
					typeName = declaringType.name;
					currentModifiers |= ClassFileConstants.AccEnum;
				} else {
					// regular field
					typeName = CharOperation.concatWith(fieldDeclaration.type.getParameterizedTypeName(), '.');
				}
				ISourceElementRequestor.FieldInfo fieldInfo = new ISourceElementRequestor.FieldInfo();
				fieldInfo.typeAnnotated = ((fieldDeclaration.bits & ASTNode.HasTypeAnnotations) != 0);
				fieldInfo.declarationStart = fieldDeclaration.declarationSourceStart;
				fieldInfo.name = fieldDeclaration.name;
				fieldInfo.modifiers = deprecated ? (currentModifiers & ExtraCompilerModifiers.AccJustFlag) | ClassFileConstants.AccDeprecated : currentModifiers & ExtraCompilerModifiers.AccJustFlag;
				fieldInfo.type = typeName;
				fieldInfo.nameSourceStart = fieldDeclaration.sourceStart;
				fieldInfo.nameSourceEnd = fieldDeclaration.sourceEnd;
				fieldInfo.categories = this.nodesToCategories.get(fieldDeclaration);
				fieldInfo.annotations = fieldDeclaration.annotations;
				fieldInfo.node = fieldDeclaration;
				this.requestor.enterField(fieldInfo);
			}
			this.visitIfNeeded(fieldDeclaration, declaringType);
			if (isInRange){
				this.requestor.exitField(
					// filter out initializations that are not a constant (simple check)
					(fieldDeclaration.initialization == null
							|| fieldDeclaration.initialization instanceof ArrayInitializer
							|| fieldDeclaration.initialization instanceof AllocationExpression
							|| fieldDeclaration.initialization instanceof ArrayAllocationExpression
							|| fieldDeclaration.initialization instanceof Assignment
							|| fieldDeclaration.initialization instanceof ClassLiteralAccess
							|| fieldDeclaration.initialization instanceof MessageSend
							|| fieldDeclaration.initialization instanceof ArrayReference
							|| fieldDeclaration.initialization instanceof ThisReference) ?
						-1 :
						fieldDeclaration.initialization.sourceStart,
					fieldEndPosition,
					fieldDeclaration.declarationSourceEnd);
			}
			break;
		case AbstractVariableDeclaration.INITIALIZER:
			if (isInRange){
				this.requestor.enterInitializer(
					fieldDeclaration.declarationSourceStart,
					fieldDeclaration.modifiers);
			}
			this.visitIfNeeded((Initializer)fieldDeclaration);
			if (isInRange){
				this.requestor.exitInitializer(fieldDeclaration.declarationSourceEnd);
			}
			break;
	}
}
protected void notifySourceElementRequestor(
	ImportReference importReference,
	boolean isPackage) {
	if (isPackage) {
		this.requestor.acceptPackage(importReference);
	} else {
		final boolean onDemand = (importReference.bits & ASTNode.OnDemand) != 0;
		this.requestor.acceptImport(
			importReference.declarationSourceStart,
			importReference.declarationSourceEnd,
			importReference.sourceStart,
			onDemand ? importReference.trailingStarPosition : importReference.sourceEnd,
			importReference.tokens,
			onDemand,
			importReference.modifiers);
	}
}
protected void notifySourceElementRequestor(ModuleDeclaration moduleDeclaration) {
	boolean isInRange =
			this.initialPosition <= moduleDeclaration.declarationSourceStart
			&& this.eofPosition >= moduleDeclaration.declarationSourceEnd;
	ISourceElementRequestor.ModuleInfo info = new ISourceElementRequestor.ModuleInfo();
	if (isInRange) {

		int currentModifiers = moduleDeclaration.modifiers;

		// remember deprecation so as to not lose it below
		boolean deprecated = (currentModifiers & ClassFileConstants.AccDeprecated) != 0 || hasDeprecatedAnnotation(moduleDeclaration.annotations);

		info.declarationStart = moduleDeclaration.declarationSourceStart;
		info.modifiers = deprecated ? (currentModifiers & ExtraCompilerModifiers.AccJustFlag) | ClassFileConstants.AccDeprecated : currentModifiers & ExtraCompilerModifiers.AccJustFlag;
		info.name = TypeConstants.MODULE_INFO_NAME;
		info.nameSourceStart = moduleDeclaration.sourceStart;
		info.nameSourceEnd = moduleDeclaration.sourceEnd;
		info.moduleName = moduleDeclaration.moduleName;
		info.annotations = moduleDeclaration.annotations;
		info.node = moduleDeclaration;
		info.categories = this.nodesToCategories.get(moduleDeclaration);
		fillModuleInfo(moduleDeclaration, info);
		this.requestor.enterModule(info);
		this.requestor.exitModule(moduleDeclaration.declarationSourceEnd);
	}
}
protected void notifySourceElementRequestor(TypeDeclaration typeDeclaration, boolean notifyTypePresence, TypeDeclaration declaringType, ImportReference currentPackage) {

	if (CharOperation.equals(TypeConstants.PACKAGE_INFO_NAME, typeDeclaration.name)) return;

	// range check
	boolean isInRange =
		this.initialPosition <= typeDeclaration.declarationSourceStart
		&& this.eofPosition >= typeDeclaration.declarationSourceEnd;

	FieldDeclaration[] fields = typeDeclaration.fields;
	AbstractMethodDeclaration[] methods = typeDeclaration.methods;
	TypeDeclaration[] memberTypes = typeDeclaration.memberTypes;
	int fieldCounter = fields == null ? 0 : fields.length;
	int methodCounter = methods == null ? 0 : methods.length;
	int memberTypeCounter = memberTypes == null ? 0 : memberTypes.length;
	int fieldIndex = 0;
	int methodIndex = 0;
	int memberTypeIndex = 0;

	if (notifyTypePresence){
		char[][] interfaceNames = getInterfaceNames(typeDeclaration);
		int kind = TypeDeclaration.kind(typeDeclaration.modifiers);
		char[] implicitSuperclassName = TypeConstants.CharArray_JAVA_LANG_OBJECT;
		ISourceElementRequestor.TypeInfo typeInfo = new ISourceElementRequestor.TypeInfo();
		typeInfo.typeAnnotated = ((typeDeclaration.bits & ASTNode.HasTypeAnnotations) != 0);
		if (isInRange) {
			int currentModifiers = typeDeclaration.modifiers;

			// remember deprecation so as to not lose it below
			boolean deprecated = (currentModifiers & ClassFileConstants.AccDeprecated) != 0 || hasDeprecatedAnnotation(typeDeclaration.annotations);

			boolean isEnumInit = typeDeclaration.allocation != null && typeDeclaration.allocation.enumConstant != null;
			char[] superclassName;
			if (isEnumInit) {
				currentModifiers |= ClassFileConstants.AccEnum;
				superclassName = declaringType.name;
			} else {
				superclassName = getSuperclassName(typeDeclaration);
			}
			if (typeDeclaration.allocation == null) {
				typeInfo.declarationStart = typeDeclaration.declarationSourceStart;
			} else if (isEnumInit) {
				typeInfo.declarationStart = typeDeclaration.allocation.enumConstant.sourceStart;
			} else {
				typeInfo.declarationStart = typeDeclaration.allocation.sourceStart;
			}
			typeInfo.modifiers = deprecated ? (currentModifiers & ExtraCompilerModifiers.AccJustFlag) | ClassFileConstants.AccDeprecated : currentModifiers & ExtraCompilerModifiers.AccJustFlag;
			typeInfo.name = typeDeclaration.name;
			typeInfo.nameSourceStart = isEnumInit ? typeDeclaration.allocation.enumConstant.sourceStart : typeDeclaration.sourceStart;
			typeInfo.nameSourceEnd = sourceEnd(typeDeclaration);
			typeInfo.superclass = superclassName;
			typeInfo.superinterfaces = interfaceNames;
			typeInfo.typeParameters = getTypeParameterInfos(typeDeclaration.typeParameters);
			typeInfo.categories = this.nodesToCategories.get(typeDeclaration);
			typeInfo.secondary = typeDeclaration.isSecondary();
			typeInfo.anonymousMember = typeDeclaration.allocation != null && typeDeclaration.allocation.enclosingInstance != null;
			typeInfo.annotations = typeDeclaration.annotations;
			typeInfo.extraFlags = ExtraFlags.getExtraFlags(typeDeclaration);
			typeInfo.node = typeDeclaration;
			this.requestor.enterType(typeInfo);
			switch (kind) {
				case TypeDeclaration.CLASS_DECL :
					if (superclassName != null)
						implicitSuperclassName = superclassName;
					break;
				case TypeDeclaration.INTERFACE_DECL :
					implicitSuperclassName = TypeConstants.CharArray_JAVA_LANG_OBJECT;
					break;
				case TypeDeclaration.ENUM_DECL :
					implicitSuperclassName = TypeConstants.CharArray_JAVA_LANG_ENUM;
					break;
				case TypeDeclaration.ANNOTATION_TYPE_DECL :
					implicitSuperclassName = TypeConstants.CharArray_JAVA_LANG_ANNOTATION_ANNOTATION;
					break;
			}
		}
		if (this.nestedTypeIndex == this.typeNames.length) {
			// need a resize
			System.arraycopy(this.typeNames, 0, (this.typeNames = new char[this.nestedTypeIndex * 2][]), 0, this.nestedTypeIndex);
			System.arraycopy(this.superTypeNames, 0, (this.superTypeNames = new char[this.nestedTypeIndex * 2][]), 0, this.nestedTypeIndex);
		}
		this.typeNames[this.nestedTypeIndex] = typeDeclaration.name;
		this.superTypeNames[this.nestedTypeIndex++] = implicitSuperclassName;
	}
	while ((fieldIndex < fieldCounter)
			|| (memberTypeIndex < memberTypeCounter)
			|| (methodIndex < methodCounter)) {
		FieldDeclaration nextFieldDeclaration = null;
		AbstractMethodDeclaration nextMethodDeclaration = null;
		TypeDeclaration nextMemberDeclaration = null;

		int position = Integer.MAX_VALUE;
		int nextDeclarationType = -1;
		if (fieldIndex < fieldCounter) {
			nextFieldDeclaration = fields[fieldIndex];
			if (nextFieldDeclaration.declarationSourceStart < position) {
				position = nextFieldDeclaration.declarationSourceStart;
				nextDeclarationType = 0; // FIELD
			}
		}
		if (methodIndex < methodCounter) {
			nextMethodDeclaration = methods[methodIndex];
			if (nextMethodDeclaration.declarationSourceStart < position) {
				position = nextMethodDeclaration.declarationSourceStart;
				nextDeclarationType = 1; // METHOD
			}
		}
		if (memberTypeIndex < memberTypeCounter) {
			nextMemberDeclaration = memberTypes[memberTypeIndex];
			if (nextMemberDeclaration.declarationSourceStart < position) {
				position = nextMemberDeclaration.declarationSourceStart;
				nextDeclarationType = 2; // MEMBER
			}
		}
		switch (nextDeclarationType) {
			case 0 :
				fieldIndex++;
				notifySourceElementRequestor(nextFieldDeclaration, typeDeclaration);
				break;
			case 1 :
				methodIndex++;
				notifySourceElementRequestor(nextMethodDeclaration, typeDeclaration, currentPackage);
				break;
			case 2 :
				memberTypeIndex++;
				notifySourceElementRequestor(nextMemberDeclaration, true, null, currentPackage);
		}
	}
	if (notifyTypePresence){
		if (isInRange){
			this.requestor.exitType(typeDeclaration.declarationSourceEnd);
		}
		this.nestedTypeIndex--;
	}
}
private void fillModuleInfo(ModuleDeclaration mod, ISourceElementRequestor.ModuleInfo modInfo) {
	if (mod.requiresCount > 0) {
		ISourceElementRequestor.RequiresInfo reqs[] = new ISourceElementRequestor.RequiresInfo[mod.requiresCount];
		for (int i = 0; i < mod.requiresCount; i++) {
			ISourceElementRequestor.RequiresInfo req = new ISourceElementRequestor.RequiresInfo();
			req.moduleName = CharOperation.concatWith(mod.requires[i].module.tokens, '.');
			req.modifiers = mod.requires[i].modifiers;
			reqs[i] = req;
		}
		modInfo.requires = reqs;
	}
	if (mod.exportsCount > 0) {
		ISourceElementRequestor.PackageExportInfo exps[] = new ISourceElementRequestor.PackageExportInfo[mod.exportsCount];
		for (int i = 0; i < mod.exportsCount; i++) {
			ISourceElementRequestor.PackageExportInfo exp = new ISourceElementRequestor.PackageExportInfo();
			ExportsStatement exportsStatement = mod.exports[i];
			exp.pkgName = exportsStatement.pkgName;
			if (exportsStatement.targets == null) {
				exp.targets = CharOperation.NO_CHAR_CHAR;
			} else {
				exp.targets = new char[exportsStatement.targets.length][];
				for(int j = 0; j < exp.targets.length; j++) {
					exp.targets[j] = CharOperation.concatWith(exportsStatement.targets[j].tokens, '.');
				}
			}
			exps[i] = exp;
		}					
		modInfo.exports = exps;
	}
	if (mod.servicesCount > 0) {
		ISourceElementRequestor.ServicesInfo[] services = new ISourceElementRequestor.ServicesInfo[mod.servicesCount];
		for (int i = 0; i < services.length; i++) {
			ISourceElementRequestor.ServicesInfo ser = new ISourceElementRequestor.ServicesInfo();
			ser.serviceName = CharOperation.concatWith(mod.services[i].serviceInterface.getParameterizedTypeName(), '.');
			ser.implNames = new char[mod.services[i].implementations.length][];
			for (int j = 0; j < ser.implNames.length; j++) {
				ser.implNames[j] = CharOperation.concatWith(mod.services[i].implementations[j].getParameterizedTypeName(), '.');
			}
			services[i] = ser;
		}
		modInfo.services = services;
	}
	if (mod.usesCount > 0) {
		char[][] uses = new char[mod.usesCount][];
		for (int i = 0; i < uses.length; i++) {
			uses[i] = CharOperation.concatWith(mod.uses[i].serviceInterface.getParameterizedTypeName(), '.');
		}
		modInfo.usedServices = uses;
	}
	if (mod.opensCount > 0) {
		ISourceElementRequestor.PackageExportInfo opens[] = new ISourceElementRequestor.PackageExportInfo[mod.opensCount];
		for (int i = 0; i < mod.opensCount; i++) {
			ISourceElementRequestor.PackageExportInfo op = new ISourceElementRequestor.PackageExportInfo();
			OpensStatement openStmt = mod.opens[i];
			op.pkgName = openStmt.pkgName;
			if (openStmt.targets == null) {
				op.targets = CharOperation.NO_CHAR_CHAR;
			} else {
				op.targets = new char[openStmt.targets.length][];
				for(int j = 0; j < op.targets.length; j++) {
					op.targets[j] = CharOperation.concatWith(openStmt.targets[j].tokens, '.');
				}
			}
			opens[i] = op;
		}
		modInfo.opens = opens;
	}
}
/*
 * Sort the given ast nodes by their positions.
 */
private static void quickSort(ASTNode[] sortedCollection, int left, int right) {
	int original_left = left;
	int original_right = right;
	ASTNode mid = sortedCollection[left +  (right - left) / 2];
	do {
		while (sortedCollection[left].sourceStart < mid.sourceStart) {
			left++;
		}
		while (mid.sourceStart < sortedCollection[right].sourceStart) {
			right--;
		}
		if (left <= right) {
			ASTNode tmp = sortedCollection[left];
			sortedCollection[left] = sortedCollection[right];
			sortedCollection[right] = tmp;
			left++;
			right--;
		}
	} while (left <= right);
	if (original_left < right) {
		quickSort(sortedCollection, original_left, right);
	}
	if (left < original_right) {
		quickSort(sortedCollection, left, original_right);
	}
}
private void reset() {
	this.typeNames = new char[4][];
	this.superTypeNames = new char[4][];
	this.nestedTypeIndex = 0;

	this.sourceEnds = null;
}
private int sourceEnd(TypeDeclaration typeDeclaration) {
	if ((typeDeclaration.bits & ASTNode.IsAnonymousType) != 0) {
		QualifiedAllocationExpression allocation = typeDeclaration.allocation;
		if (allocation.enumConstant != null) // case of enum constant body
			return allocation.enumConstant.sourceEnd;
		return allocation.type.sourceEnd;
	} else {
		return typeDeclaration.sourceEnd;
	}
}
private void visitIfNeeded(AbstractMethodDeclaration method) {
	if (this.localDeclarationVisitor != null
		&& (method.bits & ASTNode.HasLocalType) != 0) {
			if (method instanceof ConstructorDeclaration) {
				ConstructorDeclaration constructorDeclaration = (ConstructorDeclaration) method;
				if (constructorDeclaration.constructorCall != null) {
					constructorDeclaration.constructorCall.traverse(this.localDeclarationVisitor, method.scope);
				}
			}
			if (method.statements != null) {
				int statementsLength = method.statements.length;
				for (int i = 0; i < statementsLength; i++)
					method.statements[i].traverse(this.localDeclarationVisitor, method.scope);
			}
	}
}

private void visitIfNeeded(FieldDeclaration field, TypeDeclaration declaringType) {
	if (this.localDeclarationVisitor != null
		&& (field.bits & ASTNode.HasLocalType) != 0) {
			if (field.initialization != null) {
				try {
					this.localDeclarationVisitor.pushDeclaringType(declaringType);
					field.initialization.traverse(this.localDeclarationVisitor, (MethodScope) null);
				} finally {
					this.localDeclarationVisitor.popDeclaringType();
				}
			}
	}
}

private void visitIfNeeded(Initializer initializer) {
	if (this.localDeclarationVisitor != null
		&& (initializer.bits & ASTNode.HasLocalType) != 0) {
			if (initializer.block != null) {
				initializer.block.traverse(this.localDeclarationVisitor, null);
			}
	}
}
}
