/*******************************************************************************
 * Copyright (c) 2000, 2022 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
 *     Jesper Steen Moller - bug 341232
 *******************************************************************************/
package org.eclipse.jdt.internal.debug.eval.ast.engine;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Stack;

import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.SourceRange;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
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.ArrayType;
import org.eclipse.jdt.core.dom.AssertStatement;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BlockComment;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.BooleanLiteral;
import org.eclipse.jdt.core.dom.BreakStatement;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.CharacterLiteral;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConditionalExpression;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.ContinueStatement;
import org.eclipse.jdt.core.dom.DoStatement;
import org.eclipse.jdt.core.dom.EmptyStatement;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.Initializer;
import org.eclipse.jdt.core.dom.InstanceofExpression;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.LabeledStatement;
import org.eclipse.jdt.core.dom.LineComment;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MemberRef;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.MethodRef;
import org.eclipse.jdt.core.dom.MethodRefParameter;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.NullLiteral;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.QualifiedType;
import org.eclipse.jdt.core.dom.RecordDeclaration;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.SwitchCase;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.SynchronizedStatement;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.core.dom.TextBlock;
import org.eclipse.jdt.core.dom.TextElement;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.ThrowStatement;
import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclarationStatement;
import org.eclipse.jdt.core.dom.TypeLiteral;
import org.eclipse.jdt.core.dom.TypeParameter;
import org.eclipse.jdt.core.dom.UnionType;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.WhileStatement;
import org.eclipse.jdt.core.dom.WildcardType;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;

public class SourceBasedSourceGenerator extends ASTVisitor {

	private static final String RUN_METHOD_NAME = "___run"; //$NON-NLS-1$
	private static final String EVAL_METHOD_NAME = "___eval"; //$NON-NLS-1$
	private static final String EVAL_FIELD_NAME = "___field"; //$NON-NLS-1$

	private String[] fLocalVariableTypeNames;
	private String[] fLocalVariableNames;
	private String fCodeSnippet;

	private boolean fRightTypeFound;

	private boolean fCreateInAStaticMethod;

	private boolean fEvaluateNextEndTypeDeclaration;

	private String fError;

	private IType fType;
	private int fLine;

	private StringBuilder fSource;

	private String fLastTypeName;

	private String fCompilationUnitName;

	private int fSnippetStartPosition;
	private int fRunMethodStartOffset;
	private int fRunMethodLength;

	/**
	 * Level of source code to generate (major, minor). For example 1 and 4
	 * indicates 1.4.
	 */
	private int fSourceMajorLevel;
	private int fSourceMinorLevel;

	private Stack<Map<String, String>> fTypeParameterStack = new Stack<>();
	private Map<String, String> fMatchingTypeParameters = null;

	private enum TypeParameterLocation {
		TYPE, METHOD, EMPTY;
	}

	private Stack<TypeParameterLocation> fTypeParameterTypeStack = new Stack<>();
	private CompilationUnit fCompilationUnit;
	{
		fTypeParameterStack.push(Collections.<String,String>emptyMap());
		fTypeParameterTypeStack.push(TypeParameterLocation.EMPTY);
	}

	/**
	 * if the <code>createInAnInstanceMethod</code> flag is set, the method
	 * created which contains the code snippet is an no-static method, even if
	 * <code>position</code> is in a static method.
	 *
	 * @param type
	 *            the root {@link IType}
	 * @param sourcePosition
	 *            the reference position in the type's source
	 * @param createInAStaticMethod
	 *            if the source should be generated
	 * @param localTypesNames
	 *            the array of local type names
	 * @param localVariables
	 *            the listing of local variable names
	 * @param codeSnippet
	 *            the code snippet
	 * @param sourceLevel
	 *            the desired source level
	 */
	public SourceBasedSourceGenerator(IType type,
			int line, boolean createInAStaticMethod, String[] localTypesNames,
			String[] localVariables, String codeSnippet, String sourceLevel) {
		fRightTypeFound = false;
		fType = type;
		fLine = line;
		fLocalVariableTypeNames = localTypesNames;
		fLocalVariableNames = localVariables;
		fCodeSnippet = codeSnippet;
		fCreateInAStaticMethod = createInAStaticMethod;
		int index = sourceLevel.indexOf('.');
		String num;
		if (index != -1) {
			num = sourceLevel.substring(0, index);
		} else {
			num = sourceLevel;
		}
		fSourceMajorLevel = Integer.parseInt(num);
		if (index != -1) {
			num = sourceLevel.substring(index + 1);
			fSourceMinorLevel = Integer.parseInt(num);
		} else {
			fSourceMinorLevel = 0;
		}
	}

	/**
	 * Returns the generated source or <code>null</code> if no source can be
	 * generated.
	 *
	 * @return returns the backing source from the generator
	 */
	public String getSource() {
		if (fSource == null) {
			return null;
		}
		return fSource.toString();
	}

	public String getCompilationUnitName() {
		return fCompilationUnitName;
	}

	public int getSnippetStart() {
		return fSnippetStartPosition;
	}

	public int getRunMethodStart() {
		return fSnippetStartPosition - fRunMethodStartOffset;
	}

	public int getRunMethodLength() {
		return fRunMethodLength;
	}

	private boolean rightTypeFound() {
		return fRightTypeFound;
	}

	private void setRightTypeFound(boolean value) {
		fRightTypeFound = value;
	}

	public boolean hasError() {
		return fError != null;
	}

	public void setError(String errorDesc) {
		fError = errorDesc;
	}

	public String getError() {
		return fError;
	}

	private StringBuilder buildRunMethod(List<BodyDeclaration> bodyDeclarations) {
		StringBuilder buffer = new StringBuilder();

		if (fCreateInAStaticMethod) {
			buffer.append("static "); //$NON-NLS-1$
		}

		adddTypeParameters(buffer);

		buffer.append("void "); //$NON-NLS-1$
		buffer.append(getUniqueMethodName(RUN_METHOD_NAME, bodyDeclarations));
		buffer.append('(');
		for (int i = 0, length = fLocalVariableNames.length; i < length; i++) {
			buffer.append(getDotName(fLocalVariableTypeNames[i]));
			buffer.append(' ');
			buffer.append(fLocalVariableNames[i]);
			if (i + 1 < length)
			 {
				buffer.append(", "); //$NON-NLS-1$
			}
		}
		buffer.append(") throws Throwable {"); //$NON-NLS-1$
		buffer.append('\n');
		fSnippetStartPosition = buffer.length() - 2;
		fRunMethodStartOffset = fSnippetStartPosition;
		String codeSnippet = new String(fCodeSnippet).trim();

		buffer.append(codeSnippet);

		buffer.append('\n');
		buffer.append('}').append('\n');
		fRunMethodLength = buffer.length();
		return buffer;
	}

	private String getDotName(String typeName) {
		return typeName.replace('$', '.');
	}

	/**
	 * Adds generic type parameters as needed to the given buffer
	 *
	 * @param buffer
	 * @since 3.8.0
	 */
	void adddTypeParameters(StringBuilder buffer) {
		if (isSourceLevelGreaterOrEqual(1, 5)) {
			Collection<String> activeTypeParameters = Optional.ofNullable(fMatchingTypeParameters)
					.map(Map::values).orElse(Collections.emptyList());

			if (!activeTypeParameters.isEmpty()) {
				Iterator<String> iterator = activeTypeParameters.iterator();
				buffer.append(Signature.C_GENERIC_START);
				while (iterator.hasNext()) {
					String name = iterator.next();
					buffer.append(name);
					if (iterator.hasNext()) {
						buffer.append(", "); //$NON-NLS-1$
					}
				}
				buffer.append(Signature.C_GENERIC_END);
				buffer.append(' ');
			}
		}
	}

	/**
	 * Returns if the specified {@link ASTNode} has the 'correct' parent type to
	 * match the current type name context
	 *
	 * @param node
	 *            the {@link ASTNode} to check source ranges for
	 * @return true if the parent type of the given node matches the current
	 *         type name context, false otherwise
	 */
	private boolean isRightType(ASTNode node) {
		try {
			switch(node.getNodeType()) {
				case ASTNode.ANNOTATION_TYPE_DECLARATION:
				case ASTNode.ENUM_DECLARATION:
				case ASTNode.TYPE_DECLARATION: {
					AbstractTypeDeclaration decl = (AbstractTypeDeclaration) node;
					SimpleName name = decl.getName();
					ISourceRange range = new SourceRange(name.getStartPosition(), name.getLength());
					return fType.getNameRange().equals(range);
				}
				case ASTNode.ANONYMOUS_CLASS_DECLARATION: {
					return isRightType(node.getParent());
				}
				case ASTNode.CLASS_INSTANCE_CREATION: {
					ClassInstanceCreation decl = (ClassInstanceCreation) node;
					Type type = decl.getType();
					ISourceRange name = fType.getNameRange();
					return name.getOffset() >= type.getStartPosition() &&
							name.getOffset()+name.getLength() <= type.getStartPosition()+type.getLength();
				}
			}
		}
		catch(JavaModelException jme) {
			JDIDebugPlugin.log(jme);
		}
		return false;
	}

	private StringBuilder buildTypeBody(StringBuilder buffer, List<BodyDeclaration> list) {
		StringBuilder source = new StringBuilder();

		source.append('{').append('\n');

		if (buffer != null) {
			fSnippetStartPosition += source.length();
		}

		source.append(buildBody(buffer, list));
		source.append('}').append('\n');

		return source;
	}

	private StringBuilder buildEnumBody(StringBuilder buffer,
			List<EnumConstantDeclaration> constantDeclarations, List<BodyDeclaration> bodyDeclarations) {
		StringBuilder source = new StringBuilder();

		source.append('{').append('\n');
		if (constantDeclarations.isEmpty()) {
			source.append(';').append('\n');
		} else {
			for (Iterator<EnumConstantDeclaration> iter = constantDeclarations.iterator(); iter
					.hasNext();) {
				source.append(iter.next().getName()
						.getIdentifier());
				if (iter.hasNext()) {
					source.append(',');
				} else {
					source.append(';');
				}
				source.append('\n');
			}
		}

		if (buffer != null) {
			fSnippetStartPosition += source.length();
		}

		source.append(buildBody(buffer, bodyDeclarations));
		source.append('}').append('\n');

		return source;

	}

	/**
	 * Builds up the given buffer with the source from each of
	 * {@link BodyDeclaration}s in the given list
	 *
	 * @param buffer
	 *            the buffer to clone and append to
	 * @param list
	 *            the list of {@link BodyDeclaration}s
	 * @return the new source buffer
	 */
	private StringBuilder buildBody(StringBuilder buffer, List<BodyDeclaration> list) {
		StringBuilder source = new StringBuilder();
		if (buffer != null) {
			fSnippetStartPosition += source.length();
			source.append(buffer.toString());
		}
		for (BodyDeclaration bodyDeclaration : list) {
			if (bodyDeclaration instanceof FieldDeclaration) {
				source.append(buildFieldDeclaration((FieldDeclaration) bodyDeclaration));
			} else if (bodyDeclaration instanceof MethodDeclaration) {
				source.append(buildMethodDeclaration((MethodDeclaration) bodyDeclaration));
			} else if (bodyDeclaration instanceof TypeDeclaration) {
				TypeDeclaration typeDeclaration = (TypeDeclaration) bodyDeclaration;
				if (!typeDeclaration.getName().getIdentifier()
						.equals(fLastTypeName)) {
					source.append(buildTypeDeclaration(null, typeDeclaration));
				}
			} else if (bodyDeclaration instanceof EnumDeclaration) {
				EnumDeclaration enumDeclaration = (EnumDeclaration) bodyDeclaration;
				if (!enumDeclaration.getName().getIdentifier()
						.equals(fLastTypeName)) {
					source.append(buildEnumDeclaration(null, enumDeclaration));
				}
			} else if (bodyDeclaration instanceof RecordDeclaration) {
				var recordDeclaration = (RecordDeclaration) bodyDeclaration;
				if (!recordDeclaration.getName().getIdentifier().equals(fLastTypeName)) {
					source.append(buildRecordDeclaration(null, recordDeclaration));
				}
			}
		}
		return source;
	}

	private StringBuilder buildFieldDeclaration(FieldDeclaration fieldDeclaration) {
		StringBuilder source = new StringBuilder();

		source.append(Flags.toString(fieldDeclaration.getModifiers()));
		source.append(' ');
		source.append(getDotName(getTypeName(fieldDeclaration.getType())));
		source.append(' ');

		boolean first = true;
		for (Iterator<VariableDeclarationFragment> iterator = fieldDeclaration.fragments().iterator(); iterator
				.hasNext();) {
			VariableDeclarationFragment variableDeclarationFragment = iterator
					.next();
			if (first) {
				first = false;
			} else {
				source.append(',');
			}
			source.append(variableDeclarationFragment.getName().getIdentifier());
			for (int i = 0, dim = variableDeclarationFragment
					.getExtraDimensions(); i < dim; i++) {
				source.append('[').append(']');
			}
		}

		source.append(';').append('\n');

		return source;
	}

	private StringBuilder buildMethodDeclaration(
			MethodDeclaration methodDeclaration) {
		StringBuilder source = new StringBuilder();
		int modifiers = methodDeclaration.getModifiers();
		source.append(Flags.toString(modifiers));
		source.append(' ');

		appendTypeParameters(source, methodDeclaration.typeParameters());

		boolean isConstructor = methodDeclaration.isConstructor();
		if (!isConstructor) {
			source.append(getDotName(getTypeName(methodDeclaration
					.getReturnType2())));
			source.append(' ');
		}

		source.append(methodDeclaration.getName().getIdentifier());
		source.append(' ').append('(');

		boolean first = true;
		for (Iterator<SingleVariableDeclaration> iterator = methodDeclaration.parameters().iterator(); iterator
				.hasNext();) {
			SingleVariableDeclaration singleVariableDeclaration = iterator
					.next();
			if (first) {
				first = false;
			} else {
				source.append(',');
			}
			source.append(getDotName(getTypeName(singleVariableDeclaration
					.getType())));
			if (singleVariableDeclaration.isVarargs()) {
				source.append("..."); //$NON-NLS-1$
			}
			source.append(' ');
			source.append(singleVariableDeclaration.getName().getIdentifier());
			appendExtraDimensions(source,
					singleVariableDeclaration.getExtraDimensions());
		}

		source.append(')');

		appendExtraDimensions(source, methodDeclaration.getExtraDimensions());

		first = true;
		for (Object exceptionType : methodDeclaration.thrownExceptionTypes()) {
			if (first) {
				first = false;
				source.append(" throws "); //$NON-NLS-1$
			} else {
				source.append(',');
			}
			source.append(getTypeName((Type) exceptionType));
		}

		if (Flags.isAbstract(modifiers) || Flags.isNative(modifiers)) {
			// No body for abstract and native methods
			source.append(";\n"); //$NON-NLS-1$
		} else {
			source.append('{').append('\n');
			if (!isConstructor) {
				source.append(getReturnExpression(methodDeclaration
						.getReturnType2()));
			}
			source.append('}').append('\n');
		}

		return source;
	}

	private void appendExtraDimensions(StringBuilder source, int extraDimension) {
		if (extraDimension > 0) {
			source.append(' ');
			for (int i = 0; i < extraDimension; i++) {
				source.append("[]"); //$NON-NLS-1$
			}
		}
	}

	private StringBuilder buildEnumDeclaration(StringBuilder buffer,
			EnumDeclaration enumDeclaration) {
		StringBuilder source = new StringBuilder();
		source.append(Flags.toString(enumDeclaration.getModifiers()));
		source.append(" enum "); //$NON-NLS-1$

		source.append(enumDeclaration.getName().getIdentifier());

		Iterator<Type> iterator = enumDeclaration.superInterfaceTypes().iterator();
		if (iterator.hasNext()) {
			source.append(" implements "); //$NON-NLS-1$
			source.append(getTypeName(iterator.next()));
			while (iterator.hasNext()) {
				source.append(',');
				source.append(getTypeName(iterator.next()));
			}
		}

		if (buffer != null) {
			fSnippetStartPosition += source.length();
		}
		source.append(buildEnumBody(buffer, enumDeclaration.enumConstants(),
				enumDeclaration.bodyDeclarations()));

		return source;
	}

	private StringBuilder buildTypeDeclaration(StringBuilder buffer,
			TypeDeclaration typeDeclaration) {

		StringBuilder source = new StringBuilder();
		source.append(Flags.toString(typeDeclaration.getModifiers()));
		if (typeDeclaration.isInterface()) {
			source.append(" interface "); //$NON-NLS-1$
		} else {
			source.append(" class "); //$NON-NLS-1$
		}

		source.append(typeDeclaration.getName().getIdentifier());

		buildTypeParameterList(source, typeDeclaration.typeParameters());

		Type superClass = typeDeclaration.getSuperclassType();
		if (superClass != null) {
			source.append(" extends "); //$NON-NLS-1$
			source.append(getTypeName(superClass));
		}

		buildSuperInterfaceTypeList(source, typeDeclaration.superInterfaceTypes().iterator(), typeDeclaration.isInterface());

		if (buffer != null) {
			fSnippetStartPosition += source.length();
		}
		source.append(buildTypeBody(buffer, typeDeclaration.bodyDeclarations()));

		return source;
	}

	void buildSuperInterfaceTypeList(StringBuilder source, Iterator<Type> superInterfaceTypes, boolean isTypeInterface) {
		if (superInterfaceTypes.hasNext()) {
			if (isTypeInterface) {
				source.append(" extends "); //$NON-NLS-1$
			} else {
				source.append(" implements "); //$NON-NLS-1$
			}
			source.append(getTypeName(superInterfaceTypes.next()));
			while (superInterfaceTypes.hasNext()) {
				source.append(',');
				source.append(getTypeName(superInterfaceTypes.next()));
			}
		}
	}

	private StringBuilder buildRecordDeclaration(StringBuilder buffer, RecordDeclaration typeDeclaration) {

		StringBuilder source = new StringBuilder();
		source.append(Flags.toString(typeDeclaration.getModifiers()));
		source.append(" record "); //$NON-NLS-1$

		source.append(typeDeclaration.getName().getIdentifier());

		buildTypeParameterList(source, typeDeclaration.typeParameters());

		boolean first = true;
		source.append('(');
		for (SingleVariableDeclaration field : (List<SingleVariableDeclaration>) typeDeclaration.recordComponents()) {
			if (first) {
				first = false;
			} else {
				source.append(',');
			}
			source.append(getTypeName(field.getType())).append(' ').append(field.getName());
		}
		source.append(')');
		buildSuperInterfaceTypeList(source, typeDeclaration.superInterfaceTypes().iterator(), false);

		if (buffer != null) {
			fSnippetStartPosition += source.length();
		}
		source.append(buildTypeBody(buffer, typeDeclaration.bodyDeclarations()));

		return source;
	}

	void buildTypeParameterList(StringBuilder source, List<TypeParameter> typeParameters) {
		if (!typeParameters.isEmpty() && isSourceLevelGreaterOrEqual(1, 5)) {
			source.append('<');
			Iterator<TypeParameter> iter = typeParameters.iterator();
			TypeParameter typeParameter = iter.next();
			source.append(typeParameter.getName().getIdentifier());
			List<Type> typeBounds = typeParameter.typeBounds();
			if (!typeBounds.isEmpty()) {
				source.append(" extends "); //$NON-NLS-1$
				Iterator<Type> iter2 = typeBounds.iterator();
				source.append(getTypeName(iter2.next()));
				while (iter2.hasNext()) {
					source.append('&');
					source.append(getTypeName(iter2.next()));
				}
			}
			while (iter.hasNext()) {
				source.append(',');
				typeParameter = iter.next();
				source.append(typeParameter.getName().getIdentifier());
				typeBounds = typeParameter.typeBounds();
				if (!typeBounds.isEmpty()) {
					source.append(" extends "); //$NON-NLS-1$
					Iterator<Type> iter2 = typeBounds.iterator();
					source.append(getTypeName(iter2.next()));
					while (iter2.hasNext()) {
						source.append('&');
						source.append(getTypeName(iter2.next()));
					}
				}
			}
			source.append('>');
		}
	}

	private StringBuilder buildCompilationUnit(StringBuilder buffer,
			CompilationUnit compilationUnit) {
		StringBuilder source = new StringBuilder();

		PackageDeclaration packageDeclaration = compilationUnit.getPackage();
		if (packageDeclaration != null) {
			source.append("package "); //$NON-NLS-1$
			source.append(getQualifiedIdentifier(packageDeclaration.getName()));
			source.append(";\n"); //$NON-NLS-1$
		}

		for (Iterator<ImportDeclaration> iterator = compilationUnit.imports().iterator(); iterator
				.hasNext();) {
			ImportDeclaration importDeclaration = iterator
					.next();
			source.append("import "); //$NON-NLS-1$
			if (importDeclaration.isStatic()) {
				source.append("static "); //$NON-NLS-1$
			}
			source.append(getQualifiedIdentifier(importDeclaration.getName()));
			if (importDeclaration.isOnDemand()) {
				source.append(".*"); //$NON-NLS-1$
			}
			source.append(";\n"); //$NON-NLS-1$
		}

		fSnippetStartPosition += source.length();
		source.append(buffer);

		for (Iterator<TypeDeclaration> iterator = compilationUnit.types().iterator(); iterator
				.hasNext();) {
			AbstractTypeDeclaration typeDeclaration = iterator.next();
			if (Flags.isPublic(typeDeclaration.getModifiers())) {
				fCompilationUnitName = typeDeclaration.getName()
						.getIdentifier();
			}
			if (!fLastTypeName
					.equals(typeDeclaration.getName().getIdentifier())) {
				if (typeDeclaration instanceof TypeDeclaration) {
					source.append(buildTypeDeclaration(null,
							(TypeDeclaration) typeDeclaration));
				} else if (typeDeclaration instanceof EnumDeclaration) {
					source.append(buildEnumDeclaration(null,
							(EnumDeclaration) typeDeclaration));
				}
			}
		}
		if (fCompilationUnitName == null) {
			// If no public class was found, the compilation unit
			// name doesn't matter.
			fCompilationUnitName = "Eval"; //$NON-NLS-1$
		}
		return source;
	}

	/**
	 * Returns a method name that will be unique in the generated source. The
	 * generated name is baseName plus as many '_' characters as necessary to
	 * not duplicate an existing method name.
	 *
	 * @param methodName
	 *            the method name to look for
	 * @param bodyDeclarations
	 *            the listing of {@link BodyDeclaration}s to search through
	 * @return the unique method name
	 */
	private String getUniqueMethodName(String methodName, List<BodyDeclaration> bodyDeclarations) {
		Iterator<BodyDeclaration> iter = bodyDeclarations.iterator();
		BodyDeclaration bodyDeclaration;
		MethodDeclaration method;
		String foundName;
		while (iter.hasNext()) {
			bodyDeclaration = iter.next();
			if (bodyDeclaration instanceof MethodDeclaration) {
				method = (MethodDeclaration) bodyDeclaration;
				foundName = method.getName().getIdentifier();
				if (foundName.startsWith(methodName)) {
					methodName = foundName + '_';
				}
			}
		}
		return methodName;
	}

	/**
	 * Returns a field name that will be unique in the generated source. The
	 * generated name is baseName plus as many '_' characters as necessary to
	 * not duplicate an existing method name.
	 *
	 * @param fieldName
	 *            the name of the field to look for
	 * @param bodyDeclarations
	 *            the list of {@link BodyDeclaration}s to search through
	 * @return the unique field name
	 */
	private String getUniqueFieldName(String fieldName, List<BodyDeclaration> bodyDeclarations) {
		Iterator<BodyDeclaration> iter = bodyDeclarations.iterator();
		BodyDeclaration bodyDeclaration;
		FieldDeclaration fieldDeclaration;
		String foundName;
		while (iter.hasNext()) {
			bodyDeclaration = iter.next();
			if (bodyDeclaration instanceof FieldDeclaration) {
				fieldDeclaration = (FieldDeclaration) bodyDeclaration;
				for (Iterator<VariableDeclarationFragment> iterator = fieldDeclaration.fragments()
						.iterator(); iterator.hasNext();) {
					foundName = iterator.next()
							.getName().getIdentifier();
					if (foundName.startsWith(fieldName)) {
						fieldName = foundName + '_';
					}
				}
			}
		}
		return fieldName;
	}

	private String getQualifiedIdentifier(Name name) {
		String typeName = ""; //$NON-NLS-1$
		while (name.isQualifiedName()) {
			QualifiedName qualifiedName = (QualifiedName) name;
			typeName = "." + qualifiedName.getName().getIdentifier() + typeName; //$NON-NLS-1$
			name = qualifiedName.getQualifier();
		}
		if (name.isSimpleName()) {
			typeName = ((SimpleName) name).getIdentifier() + typeName;
		} else {
			return null;
		}
		return typeName;
	}

	public String getTypeName(Type type) {
		if (type.isSimpleType()) {
			String name = getQualifiedIdentifier(((SimpleType) type).getName());
			if (!isSourceLevelGreaterOrEqual(1, 5)
					&& fTypeParameterStack.peek().containsKey(name)) {
				return "Object"; //$NON-NLS-1$
			}
			return name;
		} else if (type.isArrayType()) {
			return getTypeName(((ArrayType) type).getElementType()) + "[]"; //$NON-NLS-1$
		} else if (type.isPrimitiveType()) {
			return ((PrimitiveType) type).getPrimitiveTypeCode().toString();
		} else if (type.isQualifiedType()) {
			QualifiedType qualifiedType = (QualifiedType) type;
			return getTypeName(qualifiedType.getQualifier()) + '.'
					+ qualifiedType.getName().getIdentifier();
		} else if (type.isParameterizedType()) {
			ParameterizedType parameterizedType = (ParameterizedType) type;
			StringBuilder buff = new StringBuilder(
					getTypeName(parameterizedType.getType()));
			Iterator<Type> iter = parameterizedType.typeArguments().iterator();
			if (iter.hasNext() && isSourceLevelGreaterOrEqual(1, 5)) {
				buff.append('<');
				buff.append(getTypeName(iter.next()));
				while (iter.hasNext()) {
					buff.append(',');
					buff.append(getTypeName(iter.next()));
				}
				buff.append('>');
			}
			return buff.toString();
		} else if (type.isWildcardType()) {
			WildcardType wildcardType = (WildcardType) type;
			StringBuilder buff = new StringBuilder("?"); //$NON-NLS-1$
			Type bound = wildcardType.getBound();
			if (bound != null) {
				buff.append(wildcardType.isUpperBound() ? " extends " : " super "); //$NON-NLS-1$ //$NON-NLS-2$
				buff.append(getTypeName(bound));
			}
			return buff.toString();
		}
		return null;

	}

	public String getReturnExpression(Type type) {
		if (type.isSimpleType() || type.isArrayType() || type.isQualifiedType()
				|| type.isWildcardType() || type.isParameterizedType()) {
			return "return null;"; //$NON-NLS-1$
		} else if (type.isPrimitiveType()) {
			String typeName = ((PrimitiveType) type).getPrimitiveTypeCode()
					.toString();
			char char0 = typeName.charAt(0);
			if (char0 == 'v') {
				return ""; //$NON-NLS-1$
			}
			char char1 = typeName.charAt(1);
			if (char0 == 'b' && char1 == 'o') {
				return "return false;"; //$NON-NLS-1$
			}
			return "return 0;"; //$NON-NLS-1$
		}
		return null;
	}

	// ----------------------

	/**
	 * @see ASTVisitor#endVisit(ClassInstanceCreation)
	 */
	@Override
	public void endVisit(ClassInstanceCreation node) {
		if (hasError()) {
			return;
		}
		AnonymousClassDeclaration anonymousClassDeclaration = node
				.getAnonymousClassDeclaration();
		if (anonymousClassDeclaration != null) {
			if (!rightTypeFound() && isRightType(node)) {
				setRightTypeFound(true);

				fSource = buildRunMethod(anonymousClassDeclaration
						.bodyDeclarations());
				fEvaluateNextEndTypeDeclaration = true;
			}

			if (rightTypeFound()) {

				List<BodyDeclaration> bodyDeclarations = anonymousClassDeclaration
						.bodyDeclarations();

				StringBuilder source = buildTypeBody(fSource, bodyDeclarations);

				ASTNode parent = node.getParent();
				while (!(parent instanceof MethodDeclaration
						|| parent instanceof FieldDeclaration || parent instanceof Initializer)
						&& parent != null) {
					parent = parent.getParent();
				}

				fSource = new StringBuilder();

				if (parent instanceof Initializer) {
					buildAnonymousEvalMethod(true, bodyDeclarations,
							getTypeName(node.getType()), source);
				} else if (parent instanceof MethodDeclaration) {
					MethodDeclaration enclosingMethodDeclaration = (MethodDeclaration) parent;
					buildAnonymousEvalMethod(
							Flags.isStatic(enclosingMethodDeclaration
									.getModifiers()), bodyDeclarations,
							getTypeName(node.getType()), source);

				} else if (parent instanceof FieldDeclaration) {
					FieldDeclaration enclosingFieldDeclaration = (FieldDeclaration) parent;

					if (Flags
							.isStatic(enclosingFieldDeclaration.getModifiers())) {
						fSource.append("static "); //$NON-NLS-1$
					}

					Type type = getParentType(enclosingFieldDeclaration
							.getType());
					fSource.append(getQualifiedIdentifier(((SimpleType) type)
							.getName()));
					fSource.append(' ');
					fSource.append(getUniqueFieldName(EVAL_FIELD_NAME,
							bodyDeclarations));
					fSource.append(" = new "); //$NON-NLS-1$
					fSource.append(getTypeName(node.getType()));
					fSource.append("()"); //$NON-NLS-1$

					fSnippetStartPosition += fSource.length();
					fSource.append(source);
					fSource.append(";\n"); //$NON-NLS-1$

				}
				fLastTypeName = ""; //$NON-NLS-1$
			}
		}
	}

	/**
	 * Create a <code>void ____eval()</code> method considering the given
	 * {@link BodyDeclaration}s, type name and existing body source when an
	 * anonymous {@link ClassInstanceCreation} is visited. <br>
	 * <br>
	 * This method adds the new <code>___eval</code> method source to the root
	 * {@link #fSource} variable directly
	 *
	 * @param isstatic
	 *            if the keyword <code>static</code> should be added to the
	 *            method source
	 * @param bodydecls
	 *            the existing listing of {@link BodyDeclaration}s to consider
	 *            when creating the <code>___eval</code> method name
	 * @param typename
	 *            the raw type name of the type to instantiate in the
	 *            <code>___eval</code> method
	 * @param body
	 *            the existing body of source to append to the remainder of the
	 *            new method
	 * @since 3.7
	 */
	void buildAnonymousEvalMethod(boolean isstatic, List<BodyDeclaration> bodydecls,
			String typename, StringBuilder body) {
		if (isstatic) {
			fSource.append("static "); //$NON-NLS-1$
		}
		adddTypeParameters(fSource);
		fSource.append("void "); //$NON-NLS-1$
		fSource.append(getUniqueMethodName(EVAL_METHOD_NAME, bodydecls));
		fSource.append("() {\n"); //$NON-NLS-1$
		fSource.append("new "); //$NON-NLS-1$
		fSource.append(typename);
		fSource.append("()"); //$NON-NLS-1$

		fSnippetStartPosition += fSource.length();
		fSource.append(body);
		fSource.append(";}\n"); //$NON-NLS-1$
	}

	/**
	 * Recursively finds the parent {@link Type} from the given type, in the
	 * cases where the type is an {@link ArrayType} or a
	 * {@link ParameterizedType}
	 *
	 * @param type
	 *            the {@link Type}
	 * @return the parent {@link Type}
	 */
	@SuppressWarnings("deprecation")
	private Type getParentType(Type type) {
		if (type instanceof ArrayType) {
			return getParentType(((ArrayType) type).getComponentType());
		}
		if (type instanceof ParameterizedType) {
			return getParentType(((ParameterizedType) type).getType());
		}
		return type;
	}

	/**
	 * @see ASTVisitor#endVisit(CompilationUnit)
	 */
	@Override
	public void endVisit(CompilationUnit node) {
		if (hasError()) {
			return;
		}
		if (!rightTypeFound()) { // if the right type hasn't been found
			fSource = null;
			return;
		}
		fSource = buildCompilationUnit(fSource, node);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.eclipse.jdt.core.dom.ASTVisitor#endVisit(org.eclipse.jdt.core.dom
	 * .EnumDeclaration)
	 */
	@Override
	public void endVisit(EnumDeclaration node) {

		if (hasError()) {
			return;
		}

		if (!rightTypeFound() && isRightType(node)) {
			setRightTypeFound(true);

			fSource = buildRunMethod(node.bodyDeclarations());
			fEvaluateNextEndTypeDeclaration = true;
		}

		if (!fEvaluateNextEndTypeDeclaration) {
			fEvaluateNextEndTypeDeclaration = true;
			return;
		}

		if (rightTypeFound()) {

			StringBuilder source = buildEnumDeclaration(fSource, node);

			if (node.isLocalTypeDeclaration()) {
				// enclose in a method if necessary

				ASTNode parent = node.getParent();
				while (!(parent instanceof MethodDeclaration)) {
					parent = parent.getParent();
				}
				MethodDeclaration enclosingMethodDeclaration = (MethodDeclaration) parent;

				fSource = new StringBuilder();

				if (Flags.isStatic(enclosingMethodDeclaration.getModifiers())) {
					fSource.append("static "); //$NON-NLS-1$
				}

				fSource.append("void ___eval() {\n"); //$NON-NLS-1$
				fSnippetStartPosition += fSource.length();
				fSource.append(source);
				fSource.append("}\n"); //$NON-NLS-1$

				fLastTypeName = ""; //$NON-NLS-1$
			} else {
				fSource = source;
				fLastTypeName = node.getName().getIdentifier();
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#endVisit(org.eclipse.jdt.core.dom.MethodDeclaration)
	 */
	@Override
	public void endVisit(MethodDeclaration node) {
		fTypeParameterStack.pop();
		fTypeParameterTypeStack.pop();
	}

	/**
	 * @see ASTVisitor#endVisit(TypeDeclaration)
	 */
	@Override
	public void endVisit(TypeDeclaration node) {
		if (hasError()) {
			fTypeParameterStack.pop();
			fTypeParameterTypeStack.pop();
			return;
		}

		if (!rightTypeFound() && isRightType(node)) {
			setRightTypeFound(true);

			fSource = buildRunMethod(node.bodyDeclarations());
			fEvaluateNextEndTypeDeclaration = true;
		}

		if (!fEvaluateNextEndTypeDeclaration) {
			fEvaluateNextEndTypeDeclaration = true;
			fTypeParameterStack.pop();
			fTypeParameterTypeStack.pop();
			return;
		}

		if (rightTypeFound()) {

			StringBuilder source = buildTypeDeclaration(fSource, node);

			if (node.isLocalTypeDeclaration()) {
				// enclose in a method if nessecary

				ASTNode parent = node.getParent();
				while (!(parent instanceof MethodDeclaration)) {
					parent = parent.getParent();
				}
				MethodDeclaration enclosingMethodDeclaration = (MethodDeclaration) parent;

				fSource = new StringBuilder();

				if (Flags.isStatic(enclosingMethodDeclaration.getModifiers())) {
					fSource.append("static "); //$NON-NLS-1$
				}

				fSource.append("void ___eval() {\n"); //$NON-NLS-1$
				fSnippetStartPosition += fSource.length();
				fSource.append(source);
				fSource.append("}\n"); //$NON-NLS-1$

				fLastTypeName = ""; //$NON-NLS-1$
			} else {
				fSource = source;
				fLastTypeName = node.getName().getIdentifier();
			}
		}
		fTypeParameterStack.pop();
		fTypeParameterTypeStack.pop();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * AnnotationTypeDeclaration)
	 */
	@Override
	public boolean visit(AnnotationTypeDeclaration node) {
		return false;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * AnnotationTypeMemberDeclaration)
	 */
	@Override
	public boolean visit(AnnotationTypeMemberDeclaration node) {
		return false;
	}

	/**
	 * @see ASTVisitor#visit(AnonymousClassDeclaration)
	 */
	@Override
	public boolean visit(AnonymousClassDeclaration node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(ArrayAccess)
	 */
	@Override
	public boolean visit(ArrayAccess node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(ArrayCreation)
	 */
	@Override
	public boolean visit(ArrayCreation node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(ArrayInitializer)
	 */
	@Override
	public boolean visit(ArrayInitializer node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(ArrayType)
	 */
	@Override
	public boolean visit(ArrayType node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(AssertStatement)
	 */
	@Override
	public boolean visit(AssertStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(Assignment)
	 */
	@Override
	public boolean visit(Assignment node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(Block)
	 */
	@Override
	public boolean visit(Block node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * BlockComment)
	 */
	@Override
	public boolean visit(BlockComment node) {
		return false;
	}

	/**
	 * @see ASTVisitor#visit(BooleanLiteral)
	 */
	@Override
	public boolean visit(BooleanLiteral node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(BreakStatement)
	 */
	@Override
	public boolean visit(BreakStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(CastExpression)
	 */
	@Override
	public boolean visit(CastExpression node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(CatchClause)
	 */
	@Override
	public boolean visit(CatchClause node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(CharacterLiteral)
	 */
	@Override
	public boolean visit(CharacterLiteral node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(ClassInstanceCreation)
	 */
	@Override
	public boolean visit(ClassInstanceCreation node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(CompilationUnit)
	 */
	@Override
	public boolean visit(CompilationUnit node) {
		fCompilationUnit = node;
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(ConditionalExpression)
	 */
	@Override
	public boolean visit(ConditionalExpression node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(ConstructorInvocation)
	 */
	@Override
	public boolean visit(ConstructorInvocation node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(ContinueStatement)
	 */
	@Override
	public boolean visit(ContinueStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(DoStatement)
	 */
	@Override
	public boolean visit(DoStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(EmptyStatement)
	 */
	@Override
	public boolean visit(EmptyStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * EnhancedForStatement)
	 */
	@Override
	public boolean visit(EnhancedForStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * EnumConstantDeclaration)
	 */
	@Override
	public boolean visit(EnumConstantDeclaration node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * EnumDeclaration)
	 */
	@Override
	public boolean visit(EnumDeclaration node) {
		if (rightTypeFound()) {
			fEvaluateNextEndTypeDeclaration = false;
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(ExpressionStatement)
	 */
	@Override
	public boolean visit(ExpressionStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(FieldAccess)
	 */
	@Override
	public boolean visit(FieldAccess node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(FieldDeclaration)
	 */
	@Override
	public boolean visit(FieldDeclaration node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(ForStatement)
	 */
	@Override
	public boolean visit(ForStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(IfStatement)
	 */
	@Override
	public boolean visit(IfStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(ImportDeclaration)
	 */
	@Override
	public boolean visit(ImportDeclaration node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(InfixExpression)
	 */
	@Override
	public boolean visit(InfixExpression node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(Initializer)
	 */
	@Override
	public boolean visit(Initializer node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * InstanceofExpression)
	 */
	@Override
	public boolean visit(InstanceofExpression node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(Javadoc)
	 */
	@Override
	public boolean visit(Javadoc node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(LabeledStatement)
	 */
	@Override
	public boolean visit(LabeledStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * LineComment)
	 */
	@Override
	public boolean visit(LineComment node) {
		return false;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * MarkerAnnotation)
	 */
	@Override
	public boolean visit(MarkerAnnotation node) {
		return false;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.MemberRef
	 * )
	 */
	@Override
	public boolean visit(MemberRef node) {
		return false;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * MemberValuePair)
	 */
	@Override
	public boolean visit(MemberValuePair node) {
		return false;
	}

	/**
	 * @see ASTVisitor#visit(MethodDeclaration)
	 */
	@Override
	public boolean visit(MethodDeclaration node) {
		int firstLine = fCompilationUnit.getLineNumber(node.getStartPosition());
		int lastLine = fCompilationUnit.getLineNumber(node.getStartPosition() + node.getLength());

		List<TypeParameter> typeParameters = node.typeParameters();
		pushTypeParameters(typeParameters, TypeParameterLocation.METHOD);
		if (isRightType(node.getParent()) && firstLine <= fLine && fLine <= lastLine
				&& fTypeParameterTypeStack.peek() == TypeParameterLocation.METHOD) {
				fMatchingTypeParameters  = fTypeParameterStack.peek();
		}
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	private void pushTypeParameters(List<TypeParameter> typeParameters, TypeParameterLocation location) {
		if (!typeParameters.isEmpty()) {
			HashMap<String,String> newTypeParameters = new HashMap<>(fTypeParameterStack.peek());
			Iterator<TypeParameter> iterator = typeParameters.iterator();
			while (iterator.hasNext()) {
				TypeParameter typeParameter = iterator.next();
				String boundName = typeParameter.getName().getIdentifier();
				newTypeParameters.put(boundName, typeParameter.toString());
			}
			fTypeParameterStack.push(newTypeParameters); // Push the new "scope"
			fTypeParameterTypeStack.push(location);
		} else {
			fTypeParameterStack.push(fTypeParameterStack.peek()); // Push the same
			fTypeParameterTypeStack.push(fTypeParameterTypeStack.peek());
		}
	}

	/**
	 * @see ASTVisitor#visit(MethodInvocation)
	 */
	@Override
	public boolean visit(MethodInvocation node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.MethodRef
	 * )
	 */
	@Override
	public boolean visit(MethodRef node) {
		return false;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * MethodRefParameter)
	 */
	@Override
	public boolean visit(MethodRefParameter node) {
		return false;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.Modifier
	 * )
	 */
	@Override
	public boolean visit(Modifier node) {
		return false;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * NormalAnnotation)
	 */
	@Override
	public boolean visit(NormalAnnotation node) {
		return false;
	}

	/**
	 * @see ASTVisitor#visit(NullLiteral)
	 */
	@Override
	public boolean visit(NullLiteral node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(NumberLiteral)
	 */
	@Override
	public boolean visit(NumberLiteral node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(PackageDeclaration)
	 */
	@Override
	public boolean visit(PackageDeclaration node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * ParameterizedType)
	 */
	@Override
	public boolean visit(ParameterizedType node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(ParenthesizedExpression)
	 */
	@Override
	public boolean visit(ParenthesizedExpression node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(PostfixExpression)
	 */
	@Override
	public boolean visit(PostfixExpression node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(PrefixExpression)
	 */
	@Override
	public boolean visit(PrefixExpression node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(PrimitiveType)
	 */
	@Override
	public boolean visit(PrimitiveType node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(QualifiedName)
	 */
	@Override
	public boolean visit(QualifiedName node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * QualifiedType)
	 */
	@Override
	public boolean visit(QualifiedType node) {
		return false;
	}

	/**
	 * @see ASTVisitor#visit(ReturnStatement)
	 */
	@Override
	public boolean visit(ReturnStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(SimpleName)
	 */
	@Override
	public boolean visit(SimpleName node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(SimpleType)
	 */
	@Override
	public boolean visit(SimpleType node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * SingleMemberAnnotation)
	 */
	@Override
	public boolean visit(SingleMemberAnnotation node) {
		return false;
	}

	/**
	 * @see ASTVisitor#visit(SingleVariableDeclaration)
	 */
	@Override
	public boolean visit(SingleVariableDeclaration node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(StringLiteral)
	 */
	@Override
	public boolean visit(StringLiteral node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	@Override
	public boolean visit(TextBlock node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(SuperConstructorInvocation)
	 */
	@Override
	public boolean visit(SuperConstructorInvocation node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(SuperFieldAccess)
	 */
	@Override
	public boolean visit(SuperFieldAccess node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(SuperMethodInvocation)
	 */
	@Override
	public boolean visit(SuperMethodInvocation node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(SwitchCase)
	 */
	@Override
	public boolean visit(SwitchCase node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(SwitchStatement)
	 */
	@Override
	public boolean visit(SwitchStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(SynchronizedStatement)
	 */
	@Override
	public boolean visit(SynchronizedStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.TagElement
	 * )
	 */
	@Override
	public boolean visit(TagElement node) {
		return false;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * TextElement)
	 */
	@Override
	public boolean visit(TextElement node) {
		return false;
	}

	/**
	 * @see ASTVisitor#visit(ThisExpression)
	 */
	@Override
	public boolean visit(ThisExpression node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(ThrowStatement)
	 */
	@Override
	public boolean visit(ThrowStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(TryStatement)
	 */
	@Override
	public boolean visit(TryStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.UnionType
	 * )
	 */
	@Override
	public boolean visit(UnionType node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(TypeDeclaration)
	 */
	@Override
	public boolean visit(TypeDeclaration node) {
		List<TypeParameter> typeParameters = node.typeParameters();
		pushTypeParameters(typeParameters, TypeParameterLocation.TYPE);
		if (rightTypeFound()) {
			fEvaluateNextEndTypeDeclaration = false;
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(TypeDeclarationStatement)
	 */
	@Override
	public boolean visit(TypeDeclarationStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(TypeLiteral)
	 */
	@Override
	public boolean visit(TypeLiteral node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * TypeParameter)
	 */
	@Override
	public boolean visit(TypeParameter node) {
		return false;
	}

	/**
	 * @see ASTVisitor#visit(VariableDeclarationExpression)
	 */
	@Override
	public boolean visit(VariableDeclarationExpression node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(VariableDeclarationFragment)
	 */
	@Override
	public boolean visit(VariableDeclarationFragment node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(VariableDeclarationStatement)
	 */
	@Override
	public boolean visit(VariableDeclarationStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/**
	 * @see ASTVisitor#visit(WhileStatement)
	 */
	@Override
	public boolean visit(WhileStatement node) {
		if (rightTypeFound()) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.
	 * WildcardType)
	 */
	@Override
	public boolean visit(WildcardType node) {
		return false;
	}

	/**
	 * Returns whether the source to be generated is greater than or equal to
	 * the given source level.
	 *
	 * @param major
	 *            major level - e.g. 1 from 1.4
	 * @param minor
	 *            minor level - e.g. 4 from 1.4
	 * @return <code>true</code> if the given major / minor version is less than
	 *         or equal to the backing source level
	 */
	public boolean isSourceLevelGreaterOrEqual(int major, int minor) {
		return (fSourceMajorLevel > major)
				|| (fSourceMajorLevel == major && fSourceMinorLevel >= minor);
	}

	/**
	 * Appends type parameters to source.
	 *
	 * @param source
	 *            the current buffer of source to append to
	 * @param typeParameters
	 *            the list of {@link TypeParameter}s to add
	 */
	private void appendTypeParameters(StringBuilder source, List<TypeParameter> typeParameters) {
		if (!typeParameters.isEmpty() && isSourceLevelGreaterOrEqual(1, 5)) {
			source.append('<');
			Iterator<TypeParameter> iter = typeParameters.iterator();
			TypeParameter typeParameter = iter.next();
			source.append(typeParameter.getName().getIdentifier());
			List<Type> typeBounds = typeParameter.typeBounds();
			if (!typeBounds.isEmpty()) {
				source.append(" extends "); //$NON-NLS-1$
				Iterator<Type> iter2 = typeBounds.iterator();
				source.append(getTypeName(iter2.next()));
				while (iter2.hasNext()) {
					source.append('&');
					source.append(getTypeName(iter2.next()));
				}
			}
			while (iter.hasNext()) {
				source.append(',');
				typeParameter = iter.next();
				source.append(typeParameter.getName().getIdentifier());
				typeBounds = typeParameter.typeBounds();
				if (!typeBounds.isEmpty()) {
					source.append(" extends "); //$NON-NLS-1$
					Iterator<Type> iter2 = typeBounds.iterator();
					source.append(getTypeName(iter2.next()));
					while (iter2.hasNext()) {
						source.append('&');
						source.append(getTypeName(iter2.next()));
					}
				}
			}
			source.append('>');
			source.append(' ');
		}
	}
}
