/*
 * Copyright (c) 2013-2021 Chris Newland.
 * Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD
 * Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki
 */
package org.adoptopenjdk.jitwatch.model.bytecode;

import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_BCI;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_BRANCH_COUNT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_BRANCH_NOT_TAKEN;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_BRANCH_PROB;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_BRANCH_TAKEN;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_CODE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_ID;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_KIND;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_METHOD;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_NAME;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_PREALLOCATED;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_REASON;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_TYPE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_NEWLINE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.DEBUG_LOGGING;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.DEBUG_LOGGING_BYTECODE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_DOT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_NEWLINE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_PARSE_HIR;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_SLASH;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_ASSERT_NULL;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_BC;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_BRANCH;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_CALL;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_CAST_UP;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_COMMENT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_DEPENDENCY;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_DIRECT_CALL;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_ELIMINATE_ALLOCATION;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_ELIMINATE_LOCK;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_HOT_THROW;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_INLINE_FAIL;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_INLINE_SUCCESS;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_INTRINSIC;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_JVMS;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_KLASS;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_LATE_INLINE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_METHOD;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_OBSERVE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_PARSE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_PARSE_DONE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_PHASE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_PHASE_DONE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_PREDICTED_CALL;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_TYPE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_UNCOMMON_TRAP;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_VIRTUAL_CALL;

import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.adoptopenjdk.jitwatch.compilation.AbstractCompilationVisitable;
import org.adoptopenjdk.jitwatch.compilation.CompilationUtil;
import org.adoptopenjdk.jitwatch.model.AnnotationException;
import org.adoptopenjdk.jitwatch.model.BCIOpcodeMap;
import org.adoptopenjdk.jitwatch.model.Compilation;
import org.adoptopenjdk.jitwatch.model.IMetaMember;
import org.adoptopenjdk.jitwatch.model.IParseDictionary;
import org.adoptopenjdk.jitwatch.model.IReadOnlyJITDataModel;
import org.adoptopenjdk.jitwatch.model.LogParseException;
import org.adoptopenjdk.jitwatch.model.MetaClass;
import org.adoptopenjdk.jitwatch.model.PackageManager;
import org.adoptopenjdk.jitwatch.model.Tag;
import org.adoptopenjdk.jitwatch.util.ParseUtil;
import org.adoptopenjdk.jitwatch.util.TooltipUtil;
import com.chrisnewland.freelogj.Logger;
import com.chrisnewland.freelogj.LoggerFactory;

public class BytecodeAnnotationBuilder extends AbstractCompilationVisitable
{
	private static final Logger logger = LoggerFactory.getLogger(BytecodeAnnotationBuilder.class);

	private IMetaMember currentMember;

	private IReadOnlyJITDataModel model;

	private BytecodeAnnotations bcAnnotations = new BytecodeAnnotations();

	private boolean verifyBytecodeInstructions;
	private boolean processAnnotationsForInlinedMethods;

	private EnumSet<BCAnnotationType> allowedAnnotations;

	private PackageManager packageManager;

	private Set<Tag> unhandledTags = new HashSet<>();

	public BytecodeAnnotationBuilder(boolean verifyBytecodeInstructions)
	{
		this(verifyBytecodeInstructions, false);
	}

	public BytecodeAnnotationBuilder(boolean verifyBytecodeInstructions, boolean processAnnotationsForInlinedMethods)
	{
		this.verifyBytecodeInstructions = verifyBytecodeInstructions;
		this.processAnnotationsForInlinedMethods = processAnnotationsForInlinedMethods;

		ignoreTags.add(TAG_KLASS);
		ignoreTags.add(TAG_TYPE);
		ignoreTags.add(TAG_DEPENDENCY);
		ignoreTags.add(TAG_PHASE);
		ignoreTags.add(TAG_PARSE_DONE);
		ignoreTags.add(TAG_DIRECT_CALL);
		ignoreTags.add(TAG_PARSE);
		ignoreTags.add(TAG_PHASE_DONE);
		ignoreTags.add(TAG_PREDICTED_CALL);
		ignoreTags.add(TAG_OBSERVE);
		ignoreTags.add(TAG_ASSERT_NULL);
		ignoreTags.add(TAG_CAST_UP);
		ignoreTags.add(TAG_COMMENT);
	}

	public BytecodeAnnotations buildBytecodeAnnotations(final IMetaMember member, int compilationIndex, IReadOnlyJITDataModel model)
			throws AnnotationException
	{
		return buildBytecodeAnnotations(member, compilationIndex, model, EnumSet.allOf(BCAnnotationType.class));
	}

	public BytecodeAnnotations buildBytecodeAnnotations(final IMetaMember member, int compilationIndex, IReadOnlyJITDataModel model,
			EnumSet<BCAnnotationType> allowedAnnotations) throws AnnotationException
	{
		this.currentMember = member;
		this.model = model;
		this.allowedAnnotations = allowedAnnotations;

		this.packageManager = model.getPackageManager();

		bcAnnotations.clear();

		int jdkMajorVersion = model.getJDKMajorVersion();

		if (member != null)
		{
			if (!member.isCompiled())
			{
				return bcAnnotations;
			}

			Compilation compilation = member.getCompilation(compilationIndex);

			try
			{
				buildParseTagAnnotations(jdkMajorVersion, compilation);

				buildEliminationTagAnnotations(jdkMajorVersion, compilation);
			}
			catch (LogParseException e)
			{
				logger.error("Error building bytecode annotations", e);

				Throwable cause = e.getCause();

				if (cause != null)
				{
					logger.error("Cause", cause);

					if (cause instanceof AnnotationException)
					{
						throw (AnnotationException) cause;
					}
				}
			}
		}
		return bcAnnotations;
	}

	private void buildParseTagAnnotations(int jdkMajorVersion, Compilation compilation) throws LogParseException
	{
		CompilationUtil.visitParseTagsOfCompilation(compilation, this);
	}

	private void buildEliminationTagAnnotations(int jdkMajorVersion, Compilation compilation) throws LogParseException
	{	
		if (jdkMajorVersion >= 9)
		{
			CompilationUtil.visitEliminationTagsOfCompilation(compilation, this);
		}
		else
		{
			CompilationUtil.visitOptimizerTagsOfCompilation(compilation, this);
		}
	}

	@Override
	public void visitTag(Tag tag, IParseDictionary parseDictionary) throws LogParseException
	{
		switch (tag.getName())
		{
		case TAG_PARSE:
			visitTagParse(tag, parseDictionary);
			break;

		case TAG_ELIMINATE_ALLOCATION:
			visitTagEliminateAllocation(tag, parseDictionary);
			break;

		case TAG_ELIMINATE_LOCK:
			visitTagEliminateLock(tag, parseDictionary);
			break;

		default:
			handleOther(tag);
			break;
		}
	}

	private void visitTagParse(Tag tagParse, IParseDictionary parseDictionary) throws LogParseException
	{
		String methodID = tagParse.getAttributes().get(ATTR_METHOD);

		if (CompilationUtil.memberMatchesMethodID(currentMember, methodID, parseDictionary))
		{
			try
			{
				buildParseTagAnnotations(tagParse, bcAnnotations, parseDictionary);
			}
			catch (Exception e)
			{
				throw new LogParseException("Could not parse annotations", e);
			}
		}
		else
		{
			if (!isLateInline(tagParse))
			{
				logger.warn("Parse tag does not appear to be for member {}", currentMember.getMemberName());
				logger.warn("Method ID: {}\nTag was:\n{}", methodID, tagParse.toString(true));
				logger.warn("Dictionary:\n{}", parseDictionary.toString());
			}
		}
	}

	private boolean isLateInline(Tag parseTag)
	{
		boolean result = false;

		if (parseTag != null)
		{
			Tag parent = parseTag.getParent();

			if (parent != null)
			{
				String parseTagMethod = parseTag.getAttributes().get(ATTR_METHOD);

				List<Tag> lateInlines = parent.getNamedChildren(TAG_LATE_INLINE);

				for (Tag lateInline : lateInlines)
				{
					String method = lateInline.getAttributes().get(ATTR_METHOD);

					if (method != null && method.equals(parseTagMethod))
					{
						result = true;
						break;
					}
				}

			}
		}
		return result;
	}

	private void visitTagEliminateAllocation(Tag tagEliminateAllocation, IParseDictionary parseDictionary)
	{
		List<Tag> childrenJVMS = tagEliminateAllocation.getNamedChildren(TAG_JVMS);

		String typeID = tagEliminateAllocation.getAttributes().get(ATTR_TYPE);

		String typeOrKlassName = null;

		if (typeID != null)
		{
			typeOrKlassName = ParseUtil.lookupType(typeID, parseDictionary);

			if (typeOrKlassName != null)
			{
				for (Tag tagJVMS : childrenJVMS)
				{
					Map<String, String> tagJVMSAttributes = tagJVMS.getAttributes();

					String attrBCI = tagJVMSAttributes.get(ATTR_BCI);

					int bciValue = 0;

					if (attrBCI != null)
					{
						try
						{
							bciValue = Integer.parseInt(attrBCI);
						}
						catch (NumberFormatException nfe)
						{
							logger.error("Couldn't parse bci attribute {} tag {}", attrBCI, tagJVMS.toString(true));
							continue;
						}
					}
					else
					{
						logger.error("Missing bci attribute on tag {}", tagJVMS.toString(true));
					}

					String methodID = tagJVMSAttributes.get(ATTR_METHOD);

					BCIOpcodeMap bciOpcodeMap = parseDictionary.getBCIOpcodeMap(methodID);
					
					//logger.info("method {} {} {}", methodID, parseDictionary.getParseMethod(), bciOpcodeMap.entrySet());

					if (CompilationUtil.memberMatchesMethodID(currentMember, methodID, parseDictionary))
					{
						storeEliminatedAllocation(currentMember, bciValue, typeOrKlassName, bciOpcodeMap);
					}
					else if (processAnnotationsForInlinedMethods)
					{
						IMetaMember inlinedMember = findMemberForInlinedMethod(tagJVMS, parseDictionary);

						if (inlinedMember != null)
						{
							storeEliminatedAllocation(inlinedMember, bciValue, typeOrKlassName, bciOpcodeMap);
						}
						else
						{
							unhandledTags.add(tagJVMS);
						}
					}
				}
			}
			else
			{
				logger.error("Unknown type attribute {} on tag {}", typeID, tagEliminateAllocation.toString(true));
			}
		}
		else
		{
			logger.error("Missing type attribute on tag {}", tagEliminateAllocation.toString(true));
		}
	}

	private void storeEliminatedAllocation(IMetaMember member, int bciValue, String typeOrKlassName, BCIOpcodeMap bciOpcodeMap)
	{		
		Object referencedObject = null;

		MetaClass eliminatedMetaClass = packageManager.getMetaClass(typeOrKlassName);

		if (eliminatedMetaClass != null)
		{
			referencedObject = eliminatedMetaClass;
		}
		else
		{
			referencedObject = typeOrKlassName;
		}

		Opcode opcode = bciOpcodeMap.get(bciValue);
		
		boolean isInlined = (opcode != null && opcode.isInvoke());

		String annotation = buildEliminatedAllocationAnnotation(typeOrKlassName, isInlined);
		
		putAnnotation(member, bciValue, new LineAnnotation(annotation, BCAnnotationType.ELIMINATED_ALLOCATION, referencedObject));
	}

	private IMetaMember findMemberForInlinedMethod(Tag tagJVMS, IParseDictionary parseDictionary)
	{
		IMetaMember member = null;

		String methodID = tagJVMS.getAttributes().get(ATTR_METHOD);
		
		if (methodID != null)
		{
			member = ParseUtil.lookupMember(methodID, parseDictionary, model);
		}

		return member;
	}

	private void putAnnotation(IMetaMember member, int bci, LineAnnotation annotation)
	{
		if (allowedAnnotations.contains(annotation.getType()))
		{
			bcAnnotations.addAnnotation(member, bci, annotation);
		}
	}

	private void visitTagEliminateLock(Tag tagEliminateLock, IParseDictionary parseDictionary)
	{
		String kind = tagEliminateLock.getAttributes().get(ATTR_KIND);
		
		List<Tag> childrenJVMS = tagEliminateLock.getNamedChildren(TAG_JVMS);

		if (childrenJVMS.size() > 0)
		{
			for (Tag tagJVMS : childrenJVMS)
			{
				Map<String, String> tagJVMSAttributes = tagJVMS.getAttributes();

				String attrBCI = tagJVMSAttributes.get(ATTR_BCI);

				int bciValue = 0;

				if (attrBCI != null)
				{
					try
					{
						bciValue = Integer.parseInt(attrBCI);
					}
					catch (NumberFormatException nfe)
					{
						logger.error("Couldn't parse bci attribute {} tag {}", attrBCI, tagJVMS.toString(true));
						continue;
					}
				}
				else
				{
					logger.error("Missing bci attribute on tag {}", tagJVMS.toString(true));
				}

				String methodID = tagJVMSAttributes.get(ATTR_METHOD);

				BCIOpcodeMap bciOpcodeMap = parseDictionary.getBCIOpcodeMap(methodID);
				
				//logger.info("current {} methodID {} parseMethod {}", currentMember.toStringUnqualifiedMethodName(true, true), methodID, parseDictionary.getParseMethod());

				if (CompilationUtil.memberMatchesMethodID(currentMember, methodID, parseDictionary))
				{
					storeElidedLock(currentMember, bciValue, kind, bciOpcodeMap);
				}
				else if (processAnnotationsForInlinedMethods)
				{
					IMetaMember inlinedMember = findMemberForInlinedMethod(tagJVMS, parseDictionary);

					if (inlinedMember != null)
					{
						storeElidedLock(inlinedMember, bciValue, kind, bciOpcodeMap);
					}
					else
					{
						unhandledTags.add(tagJVMS);
					}
				}
				
			} // end for
		}
	}
	
	private void storeElidedLock(IMetaMember member, int bciValue, String kind, BCIOpcodeMap bciOpcodeMap)
	{		
		Opcode opcode = bciOpcodeMap.get(bciValue);
		
		boolean isInlined = (opcode != null && opcode.isInvoke());

		String annotation = buildElidedLockAnnotation(isInlined);
		
		putAnnotation(member, bciValue, new LineAnnotation(annotation, BCAnnotationType.LOCK_ELISION, kind));
	}
	
	private String buildElidedLockAnnotation(boolean isInlined)
	{
		StringBuilder builder = new StringBuilder();
		
		if (isInlined)
		{
			builder.append("A lock was eliminated due to inlining at this bci");
		}
		else
		{
			builder.append("A lock was eliminated at this bci");

		}
		
		return builder.toString();
	}

	private void visitTagUncommonTrap(Tag tag)
	{
		UncommonTrap trap = UncommonTrap.parse(tag);

		if (trap != null)
		{
			putAnnotation(currentMember, trap.getBCI(), new LineAnnotation(trap.toString(), BCAnnotationType.UNCOMMON_TRAP));
		}
	}

	private void buildParseTagAnnotations(Tag parseTag, BytecodeAnnotations annotations, IParseDictionary parseDictionary)
			throws AnnotationException
	{
		// Only interested in annotating the current method so
		// do not recurse into method or parse tags

		if (DEBUG_LOGGING)
		{
			logger.debug("Building parse tag annotations");
		}

		List<Tag> children = parseTag.getChildren();

		int currentBytecode = -1;

		Map<String, Map<String, String>> methodAttrs = new HashMap<>();
		Map<String, String> callAttrs = new HashMap<>();
		Map<String, String> lastMethodAttrs = new HashMap<>();

		String currentMethodID = parseTag.getAttributes().get(ATTR_METHOD);

		BytecodeInstruction currentInstruction = null;

		for (Tag child : children)
		{
			String name = child.getName();
			
			Map<String, String> tagAttrs = child.getAttributes();

			if (DEBUG_LOGGING_BYTECODE)
			{
				logger.debug("Examining child tag {}", child);
			}

			switch (name)
			{
			case TAG_BC:
			{
				String bciAttr = tagAttrs.get(ATTR_BCI);
				String codeAttr = tagAttrs.get(ATTR_CODE);

				currentBytecode = Integer.parseInt(bciAttr);
				int code = Integer.parseInt(codeAttr);
				callAttrs.clear();

				if (DEBUG_LOGGING_BYTECODE)
				{
					logger.debug("BC Tag {} {}", currentBytecode, code);
				}

				currentInstruction = getInstructionAtIndex(currentBytecode);

				if (DEBUG_LOGGING_BYTECODE)
				{
					logger.debug("Instruction at {} is {}", currentBytecode, currentInstruction);
				}

				break;
			}

			case TAG_CALL:
			{
				callAttrs.clear();
				callAttrs.putAll(tagAttrs);

				lastMethodAttrs.clear();
				String calleeId = tagAttrs.get("method");
				
				if (calleeId != null)
				{
					Map<String, String> calleeAttrs = methodAttrs.get(calleeId);
					
					if (calleeAttrs != null)
					{
						lastMethodAttrs.putAll(calleeAttrs);
					}
				}

				break;
			}

			case TAG_METHOD:
			{
				String methodId = tagAttrs.get("id");
				if (methodId != null)
				{
					methodAttrs.put(methodId, tagAttrs);
				}
				break;
			}

			case TAG_INLINE_SUCCESS:
			{
				if (!sanityCheckInline(currentInstruction))
				{
					throw new AnnotationException("Expected an invoke instruction (in INLINE_SUCCESS)", currentBytecode,
							currentInstruction);
				}

				String reason = tagAttrs.get(ATTR_REASON);
				String annotationText = buildInlineAnnotation(parseDictionary, lastMethodAttrs, callAttrs, reason, true);

				IMetaMember inlinedMember = ParseUtil.lookupMember(lastMethodAttrs.get(ATTR_ID), parseDictionary, model);

				LineAnnotation lineAnnotation = new LineAnnotation(annotationText, BCAnnotationType.INLINE_SUCCESS, inlinedMember);
								
				putAnnotation(currentMember, currentBytecode, lineAnnotation);

				break;
			}

			case TAG_INLINE_FAIL:
			{
				if (!sanityCheckInline(currentInstruction))
				{
					throw new AnnotationException("Expected an invoke instruction (in INLINE_FAIL)", currentBytecode,
							currentInstruction);
				}

				String reason = tagAttrs.get(ATTR_REASON);
				String annotationText = buildInlineAnnotation(parseDictionary, lastMethodAttrs, callAttrs, reason, false);
				
				IMetaMember inlinedMember = ParseUtil.lookupMember(lastMethodAttrs.get(ATTR_ID), parseDictionary, model);

				LineAnnotation lineAnnotation = new LineAnnotation(annotationText, BCAnnotationType.INLINE_FAIL, inlinedMember);
								
				putAnnotation(currentMember, currentBytecode, lineAnnotation);

				break;
			}

			case TAG_BRANCH:
			{
				if (!sanityCheckBranch(currentInstruction))
				{
					throw new AnnotationException("Expected a branch instruction (BRANCH)", currentBytecode, currentInstruction);
				}

				String branchAnnotation = buildBranchAnnotation(tagAttrs);

				putAnnotation(currentMember, currentBytecode, new LineAnnotation(branchAnnotation, BCAnnotationType.BRANCH));
				
				break;
			}

			case TAG_INTRINSIC:
			{
				if (!sanityCheckIntrinsic(currentInstruction))
				{
					throw new AnnotationException("Expected an invoke instruction (INTRINSIC)", currentBytecode,
							currentInstruction);
				}

				StringBuilder reason = new StringBuilder();
				reason.append("Intrinsic: ").append(tagAttrs.get(ATTR_ID));

				putAnnotation(currentMember, currentBytecode,
						new LineAnnotation(reason.toString(), BCAnnotationType.INTRINSIC_USED));

				break;
			}

			case TAG_UNCOMMON_TRAP:
			{
				String trapMethod = child.getAttributes().get(ATTR_METHOD);

				if (trapMethod == null || currentMethodID.equals(trapMethod))
				{
					visitTagUncommonTrap(child);
				}

				break;
			}

			case TAG_PHASE:
			{
				String phaseName = tagAttrs.get(ATTR_NAME);

				if (S_PARSE_HIR.equals(phaseName))
				{
					buildParseTagAnnotations(child, annotations, parseDictionary);
				}
				else
				{
					logger.warn("Don't know how to handle phase {}", phaseName);
				}

				break;
			}

			case TAG_VIRTUAL_CALL:
				putAnnotation(currentMember, currentBytecode,
						new LineAnnotation("Virtual call, not inlined", BCAnnotationType.VIRTUAL_CALL));
				break;

			case TAG_HOT_THROW:
			{

				MemberBytecode memberBytecode = currentMember.getMemberBytecode();

				if (memberBytecode != null)
				{
					ExceptionTable exceptionTable = memberBytecode.getExceptionTable();

					if (exceptionTable != null)
					{
						ExceptionTableEntry entry = exceptionTable.getEntryForBCI(currentBytecode);

						if (entry != null)
						{
							int exceptionBCI = entry.getTarget();

							String exceptionType = entry.getType();

							String preallocated = child.getAttributes().get(ATTR_PREALLOCATED);

							StringBuilder reason = new StringBuilder();

							reason.append(exceptionType.replaceAll(S_SLASH, S_DOT)).append(" thrown by this operation");

							BCAnnotationType annotationType;

							if (preallocated != null && "1".equals(preallocated))
							{
								reason.append(" has been pre-allocated.");
								annotationType = BCAnnotationType.HOT_THROW_PREALLOCATED;
							}
							else
							{
								reason.append(" was not pre-allocated.");
								annotationType = BCAnnotationType.HOT_THROW_NOT_PREALLOCATED;
							}

							putAnnotation(currentMember, exceptionBCI, new LineAnnotation(reason.toString(), annotationType));
						}
					}
					else
					{
						logger.warn("No ExceptionTable found for {}", currentMember);

					}
				}
				else
				{
					logger.warn("No MemberBytecode found for {}", currentMember);
				}
				break;
			}

			default:
				handleOther(child);
				break;
			}
		}
	}

	private String buildInlineAnnotation(IParseDictionary parseDictionary, Map<String, String> methodAttrs,
			Map<String, String> callAttrs, String reason, boolean inlined)
	{
		return TooltipUtil.buildInlineAnnotationText(inlined, reason, callAttrs, methodAttrs, parseDictionary);
	}

	private String buildEliminatedAllocationAnnotation(String typeOrKlassName, boolean isInlined)
	{
		StringBuilder builder = new StringBuilder();
		
		if (isInlined)
		{
			builder.append("Heap allocation");

			if (typeOrKlassName != null)
			{
				builder.append(" of type ").append(typeOrKlassName);
			}
			
			builder.append(S_NEWLINE).append("was eliminated due to inlining at this bci");
		}
		else
		{
			builder.append("Object");
			
			if (typeOrKlassName != null)
			{
				builder.append(" of type ").append(typeOrKlassName);
			}
			
			builder.append(" does not escape method.\n");
			
			builder.append("Heap allocation has been eliminated.\n");
		}
		
		return builder.toString();
	}

	private String buildBranchAnnotation(Map<String, String> tagAttrs)
	{
		String count = tagAttrs.get(ATTR_BRANCH_COUNT);
		String taken = tagAttrs.get(ATTR_BRANCH_TAKEN);
		String notTaken = tagAttrs.get(ATTR_BRANCH_NOT_TAKEN);
		String prob = tagAttrs.get(ATTR_BRANCH_PROB);

		StringBuilder reason = new StringBuilder();

		if (count != null)
		{
			reason.append("Count: ").append(count).append(C_NEWLINE);
		}

		reason.append("Branch taken: ").append(taken).append(C_NEWLINE).append("Branch not taken: ").append(notTaken);

		if (prob != null)
		{
			reason.append(C_NEWLINE).append("Taken Probability: ").append(prob);
		}

		return reason.toString();
	}

	// used to detect if class file matches log file
	// only these bytecode instruction should be at the offset
	// for an inlining log statement
	public boolean sanityCheckInline(BytecodeInstruction instr)
	{
		return verifyBytecodeInstructions ? sanityCheckInvoke(instr) : true;
	}

	public boolean sanityCheckIntrinsic(BytecodeInstruction instr)
	{
		return verifyBytecodeInstructions ? sanityCheckInvoke(instr) : true;
	}

	private boolean sanityCheckInvoke(BytecodeInstruction instr)
	{
		boolean sane = false;

		if (instr != null)
		{
			sane = instr.getOpcode().isInvoke();
		}

		return sane;
	}

	public boolean sanityCheckBranch(BytecodeInstruction instr)
	{
		boolean sane = false;

		if (instr != null)
		{
			sane = instr.getOpcode().getMnemonic().startsWith("if");
		}

		return verifyBytecodeInstructions ? sane : true;
	}

	private BytecodeInstruction getInstructionAtIndex(int index)
	{
		BytecodeInstruction found = null;

		for (BytecodeInstruction instruction : currentMember.getInstructions())
		{
			if (instruction.getOffset() == index)
			{
				found = instruction;
				break;
			}
		}

		return found;
	}

	public Set<Tag> getUnhandledTags()
	{
		return unhandledTags;
	}
}
