<commit_before>/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2003-2005 University of Maryland
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs;

import java.io.IOException;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.Type;
import org.objectweb.asm.tree.ClassNode;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.Hierarchy2;
import edu.umd.cs.findbugs.ba.JavaClassAndMethod;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.XFactory;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.ba.bcp.FieldVariable;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.FieldDescriptor;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.IAnalysisCache;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;
import edu.umd.cs.findbugs.util.ClassName;
import edu.umd.cs.findbugs.visitclass.DismantleBytecode;
import edu.umd.cs.findbugs.visitclass.PreorderVisitor;
import edu.umd.cs.findbugs.xml.XMLAttributeList;
import edu.umd.cs.findbugs.xml.XMLOutput;

/**
 * An instance of a bug pattern.
 * A BugInstance consists of several parts:
 * <p/>
 * <ul>
 * <li> the type, which is a string indicating what kind of bug it is;
 * used as a key for the FindBugsMessages resource bundle
 * <li> the priority; how likely this instance is to actually be a bug
 * <li> a list of <em>annotations</em>
 * </ul>
 * <p/>
 * The annotations describe classes, methods, fields, source locations,
 * and other relevant context information about the bug instance.
 * Every BugInstance must have at least one ClassAnnotation, which
 * describes the class in which the instance was found.  This is the
 * "primary class annotation".
 * <p/>
 * <p> BugInstance objects are built up by calling a string of <code>add</code>
 * methods.  (These methods all "return this", so they can be chained).
 * Some of the add methods are specialized to get information automatically from
 * a BetterVisitor or DismantleBytecode object.
 *
 * @author <NAME>
 * @see BugAnnotation
 */
public class BugInstance implements Comparable<BugInstance>, XMLWriteableWithMessages, Serializable, Cloneable {
	private static final long serialVersionUID = 1L;

	private String type;
	private int priority;
	private ArrayList<BugAnnotation> annotationList;
	private int cachedHashCode;
	private @CheckForNull  BugDesignation userDesignation;
	private BugProperty propertyListHead, propertyListTail;
	private String uniqueId;
	private String oldInstanceHash;
	private String instanceHash;
	private int instanceOccurrenceNum;
	private int instanceOccurrenceMax;


	/*
	 * The following fields are used for tracking Bug instances across multiple versions of software.
	 * They are meaningless in a BugCollection for just one version of software. 
	 */
	private long firstVersion = 0;
	private long lastVersion = -1;
	private boolean introducedByChangeOfExistingClass;
	private boolean removedByChangeOfPersistingClass;

	/**
	 * This value is used to indicate that the cached hashcode
	 * is invalid, and should be recomputed.
	 */
	private static final int INVALID_HASH_CODE = 0;

	/**
	 * This value is used to indicate whether BugInstances should be reprioritized very low,
	 * when the BugPattern is marked as experimental
	 */
	private static boolean adjustExperimental = false;

	private static Set<String> missingBugTypes = Collections.synchronizedSet(new HashSet<String>());
	
	/**
	 * Constructor.
	 *
	 * @param type     the bug type
	 * @param priority the bug priority
	 */
	public BugInstance(String type, int priority) {
		this.type = type;
		this.priority = priority;
		annotationList = new ArrayList<BugAnnotation>(4);
		cachedHashCode = INVALID_HASH_CODE;

		BugPattern p = I18N.instance().lookupBugPattern(type);
		if (p == null) {
			if (missingBugTypes.add(type)) {
				String msg = "Can't find definition of bug type " + type;
				AnalysisContext.logError(msg, new IllegalArgumentException(msg));
			}
		} else
			this.priority += p.getPriorityAdjustment();
		if (adjustExperimental && isExperimental())
			this.priority = Detector.EXP_PRIORITY;
		boundPriority();
	}

	private void boundPriority() {
		priority = boundedPriority(priority);
	}

	@Override
	public Object clone() {
		BugInstance dup;

		try {
			dup = (BugInstance) super.clone();

			// Do deep copying of mutable objects
			for (int i = 0; i < dup.annotationList.size(); ++i) {
				dup.annotationList.set(i, (BugAnnotation) dup.annotationList.get(i).clone());
			}
			 dup.propertyListHead = dup.propertyListTail = null;
			for (Iterator<BugProperty> i = propertyIterator(); i.hasNext(); ) {
				dup.addProperty((BugProperty) i.next().clone());
			}

			return dup;
		} catch (CloneNotSupportedException e) {
			throw new AssertionError(e);
		}
	}

	/**
	 * Create a new BugInstance.
	 * This is the constructor that should be used by Detectors.
	 * 
	 * @param detector the Detector that is reporting the BugInstance
	 * @param type     the bug type
	 * @param priority the bug priority
	 */
	public BugInstance(Detector detector, String type, int priority) {
		this(type, priority);

		if (detector != null) {
			// Adjust priority if required
			DetectorFactory factory =
				DetectorFactoryCollection.instance().getFactoryByClassName(detector.getClass().getName());
			if (factory != null) {
				this.priority += factory.getPriorityAdjustment();
				boundPriority();
			}
		}

	}

	/**
	 * Create a new BugInstance.
	 * This is the constructor that should be used by Detectors.
	 * 
	 * @param detector the Detector2 that is reporting the BugInstance
	 * @param type     the bug type
	 * @param priority the bug priority
	 */
	public BugInstance(Detector2 detector, String type, int priority) {
		this(type, priority);

		if (detector != null) {
			// Adjust priority if required
			DetectorFactory factory =
				DetectorFactoryCollection.instance().getFactoryByClassName(detector.getDetectorClassName());
			if (factory != null) {
				this.priority += factory.getPriorityAdjustment();
				boundPriority();
			}
		}

	}
	public static void setAdjustExperimental(boolean adjust) {
		adjustExperimental = adjust;
	}

	/* ----------------------------------------------------------------------
	 * Accessors
	 * ---------------------------------------------------------------------- */

	/**
	 * Get the bug type.
	 */
	public String getType() {
		return type;
	}

	/**
	 * Get the BugPattern.
	 */
	public @NonNull BugPattern getBugPattern() {
		BugPattern result =  I18N.instance().lookupBugPattern(getType());
		if (result != null) return result;
		AnalysisContext.logError("Unable to find description of bug pattern " + getType());
		result = I18N.instance().lookupBugPattern("UNKNOWN");
		if (result != null) return result;
		return BugPattern.REALLY_UNKNOWN;
	}

	/**
	 * Get the bug priority.
	 */
	public int getPriority() {
		return priority;
	}

	/**
	 * Get a string describing the bug priority and type.
	 * e.g. "High Priority Correctness"
	 * @return a string describing the bug priority and type
	 */
	public String getPriorityTypeString()
	{
		String priorityString = getPriorityString();
		BugPattern bugPattern = this.getBugPattern();
		//then get the category and put everything together
		String categoryString;
		if (bugPattern == null) categoryString = "Unknown category for " + getType();
		else categoryString = I18N.instance().getBugCategoryDescription(bugPattern.getCategory());
		return priorityString + " Priority " + categoryString;
		//TODO: internationalize the word "Priority"
	}

	public String getPriorityTypeAbbreviation()
	{
		String priorityString = getPriorityAbbreviation();
		return priorityString + " " + getCategoryAbbrev();
	}

	public String getCategoryAbbrev() {
	    BugPattern bugPattern = getBugPattern();
	    if (bugPattern == null) return "?";
		return bugPattern.getCategoryAbbrev();
    }

	public String getPriorityString() {
		//first, get the priority
		int value = this.getPriority();
		String priorityString;
		if (value == Detector.HIGH_PRIORITY)
			priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_high", "High");
		else if (value == Detector.NORMAL_PRIORITY)
			priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_normal", "Medium");
		else if (value == Detector.LOW_PRIORITY)
			priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_low", "Low");
		else if (value == Detector.EXP_PRIORITY)
			priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_experimental", "Experimental");
		else
			priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_ignore", "Ignore"); // This probably shouldn't ever happen, but what the hell, let's be complete
		return priorityString;
	}

	public String getPriorityAbbreviation() {
		return getPriorityString().substring(0,1);
	}
	/**
	 * Set the bug priority.
	 */
	public void setPriority(int p) {
		priority = boundedPriority(p);
	}

	private int boundedPriority(int p) {
		return Math.max(Detector.HIGH_PRIORITY, Math.min(Detector.IGNORE_PRIORITY, p));
	}
	public void raisePriority() {
		priority = boundedPriority(priority-1);

	}
	public void lowerPriority() {
		priority = boundedPriority(priority+1);
	}

	public void lowerPriorityALot() {
		priority = boundedPriority(priority+2);
	}

	/**
	 * Is this bug instance the result of an experimental detector?
	 */
	public boolean isExperimental() {
		BugPattern pattern = getBugPattern();
		return (pattern != null) && pattern.isExperimental();
	}

	/**
	 * Get the primary class annotation, which indicates where the bug occurs.
	 */
	public ClassAnnotation getPrimaryClass() {
		return  findAnnotationOfType(ClassAnnotation.class);
	}

	/**
	 * Get the primary method annotation, which indicates where the bug occurs.
	 */
	public MethodAnnotation getPrimaryMethod() {
		return  findAnnotationOfType(MethodAnnotation.class);
	}
	/**
	 * Get the primary method annotation, which indicates where the bug occurs.
	 */
	public FieldAnnotation getPrimaryField() {
		return findAnnotationOfType(FieldAnnotation.class);
	}


	public BugInstance lowerPriorityIfDeprecated() {
		MethodAnnotation m = getPrimaryMethod();
		if (m != null && XFactory.createXMethod(m).isDeprecated())
				lowerPriority();
		FieldAnnotation f = getPrimaryField();
		if (f != null && XFactory.createXField(f).isDeprecated())
			lowerPriority();
		return this;
	}
	/**
	 * Find the first BugAnnotation in the list of annotations
	 * that is the same type or a subtype as the given Class parameter.
	 * 
	 * @param cls the Class parameter
	 * @return the first matching BugAnnotation of the given type,
	 *         or null if there is no such BugAnnotation
	 */
	private <T extends BugAnnotation> T findAnnotationOfType(Class<T>  cls) {
		for (Iterator<BugAnnotation> i = annotationIterator(); i.hasNext();) {
			BugAnnotation annotation = i.next();
			if (cls.isAssignableFrom(annotation.getClass()))
				return cls.cast(annotation);
		}
		return null;
	}

	public LocalVariableAnnotation getPrimaryLocalVariableAnnotation() {
		for (BugAnnotation annotation : annotationList) 
			if (annotation instanceof LocalVariableAnnotation)
				return (LocalVariableAnnotation) annotation;
		return null;
	}
	/**
	 * Get the primary source line annotation.
	 * There is guaranteed to be one (unless some Detector constructed
	 * an invalid BugInstance).
	 *
	 * @return the source line annotation
	 */
	public SourceLineAnnotation getPrimarySourceLineAnnotation() {
		// Highest priority: return the first top level source line annotation
		for (BugAnnotation annotation : annotationList) {
			if (annotation instanceof SourceLineAnnotation
					&& annotation.getDescription().equals(SourceLineAnnotation.DEFAULT_ROLE))
				return (SourceLineAnnotation) annotation;
		}
		for (BugAnnotation annotation : annotationList) {
			if (annotation instanceof SourceLineAnnotation)
				return (SourceLineAnnotation) annotation;
		}

		// Next: Try primary method, primary field, primary class
		SourceLineAnnotation srcLine;
		if ((srcLine = inspectPackageMemberSourceLines(getPrimaryMethod())) != null)
			return srcLine;
		if ((srcLine = inspectPackageMemberSourceLines(getPrimaryField())) != null)
			return srcLine;
		if ((srcLine = inspectPackageMemberSourceLines(getPrimaryClass())) != null)
			return srcLine;

		// Last resort: throw exception
		throw new IllegalStateException("BugInstance must contain at least one class, method, or field annotation");
	}

	public String getInstanceKey() {
		StringBuilder buf = new StringBuilder(type);
		for (BugAnnotation annotation : annotationList) {
			if (annotation instanceof SourceLineAnnotation || annotation instanceof MethodAnnotation && !annotation.isSignificant()) {
				// do nothing
			} else {
				buf.append(":");
				buf.append(annotation.format("hash", null));
			}
		}
		return buf.toString();
	}
	/**
	 * If given PackageMemberAnnotation is non-null, return its
	 * SourceLineAnnotation.
	 * 
	 * @param packageMember
	 *            a PackageMemberAnnotation
	 * @return the PackageMemberAnnotation's SourceLineAnnotation, or null if
	 *         there is no SourceLineAnnotation
	 */
	private SourceLineAnnotation inspectPackageMemberSourceLines(PackageMemberAnnotation packageMember) {
		return (packageMember != null) ? packageMember.getSourceLines() : null;
	}

	/**
	 * Get an Iterator over all bug annotations.
	 */
	public Iterator<BugAnnotation> annotationIterator() {
		return annotationList.iterator();
	}
	
	/**
	 * Get an Iterator over all bug annotations.
	 */
	public List<? extends BugAnnotation> getAnnotations() {
		return annotationList;
	}

	/**
	 * Get the abbreviation of this bug instance's BugPattern.
	 * This is the same abbreviation used by the BugCode which
	 * the BugPattern is a particular species of.
	 */
	public String getAbbrev() {
		BugPattern pattern = getBugPattern();
		return pattern != null ? pattern.getAbbrev() : "<unknown bug pattern>";
	}

	/** set the user designation object. This will clobber any
	 *  existing annotationText (or any other BugDesignation field). */
	public void setUserDesignation(BugDesignation bd) {
		userDesignation = bd;
	}
	/** return the user designation object, which may be null.
	 * 
	 *  A previous calls to getSafeUserDesignation(), setAnnotationText(),
	 *  or setUserDesignation() will ensure it will be non-null
	 *  [barring an intervening setUserDesignation(null)].
	 *  @see #getNonnullUserDesignation() */
	@Nullable public BugDesignation getUserDesignation() {
		return userDesignation;
	}
	/** return the user designation object, creating one if
	 *  necessary. So calling
	 *  <code>getSafeUserDesignation().setDesignation("HARMLESS")</code>
	 *  will always work without the possibility of a NullPointerException.
	 *  @see #getUserDesignation() */
	@NonNull public BugDesignation getNonnullUserDesignation() {
		if (userDesignation == null)
			userDesignation = new BugDesignation();
		return userDesignation;
	}


	/** Get the user designation key.
	 *  E.g., "MOSTLY_HARMLESS", "CRITICAL", "NOT_A_BUG", etc.
	 *
	 *  If the user designation object is null,returns UNCLASSIFIED.
	 *
	 *  To set the user designation key, call
	 *  <code>getSafeUserDesignation().setDesignation("HARMLESS")</code>.
	 * 
	 *  @see I18N#getUserDesignation(String key)
	 *  @return the user designation key
	 */
	@NonNull public String getUserDesignationKey() {
		BugDesignation userDesignation = this.userDesignation;
		if (userDesignation == null) return BugDesignation.UNCLASSIFIED;
		return userDesignation.getDesignationKey();
	}

	/**
	 * Set the user annotation text.
	 *
	 * @param annotationText the user annotation text
	 */
	public void setAnnotationText(String annotationText) {
		getNonnullUserDesignation().setAnnotationText(annotationText);
	}

	/**
	 * Get the user annotation text.
	 *
	 * @return the user annotation text
	 */
	@NonNull public String getAnnotationText() {
		BugDesignation userDesignation = this.userDesignation;
		if (userDesignation == null) return "";		
		String s = userDesignation.getAnnotationText();
		if (s == null) return "";
		return s;
	}

	/**
	 * Determine whether or not the annotation text contains
	 * the given word.
	 *
	 * @param word the word
	 * @return true if the annotation text contains the word, false otherwise
	 */
	public boolean annotationTextContainsWord(String word) {
		return getTextAnnotationWords().contains(word);
	}

	/**
	 * Get set of words in the text annotation.
	 */
	public Set<String> getTextAnnotationWords() {
		HashSet<String> result = new HashSet<String>();

		StringTokenizer tok = new StringTokenizer(getAnnotationText(), " \t\r\n\f.,:;-");
		while (tok.hasMoreTokens()) {
			result.add(tok.nextToken());
		}
		return result;
	}

	/**
	 * Get the BugInstance's unique id.
	 * 
	 * @return the unique id, or null if no unique id has been assigned
	 * 
	 * Deprecated, since it isn't persistent
	 */
	@Deprecated
	public String getUniqueId() {
		return uniqueId;
	}

	/**
	 * Set the unique id of the BugInstance.
	 * 
	 * @param uniqueId the unique id
	 * 
	 *   * Deprecated, since it isn't persistent
	 */
	@Deprecated
	 void setUniqueId(String uniqueId) {
		this.uniqueId = uniqueId;
	}



	/* ----------------------------------------------------------------------
	 * Property accessors
	 * ---------------------------------------------------------------------- */

	private class BugPropertyIterator implements Iterator<BugProperty> {
		private BugProperty prev, cur;
		private boolean removed;

		/* (non-Javadoc)
		 * @see java.util.Iterator#hasNext()
		 */
		public boolean hasNext() {
			return findNext() != null;
		}
		/* (non-Javadoc)
		 * @see java.util.Iterator#next()
		 */
		public BugProperty next() {
			BugProperty next = findNext();
			if (next == null)
				throw new NoSuchElementException();
			prev = cur;
			cur = next;
			removed = false;
			return cur;
		}

		/* (non-Javadoc)
		 * @see java.util.Iterator#remove()
		 */
		public void remove() {
			if (cur == null || removed)
				throw new IllegalStateException();
			if (prev == null) {
				propertyListHead = cur.getNext();
			} else {
				prev.setNext(cur.getNext());
			}
			if (cur == propertyListTail) {
				propertyListTail = prev;
			}
			removed = true;
		}

		private BugProperty findNext() {
			return cur == null ? propertyListHead : cur.getNext();
		}

	}

	/**
	 * Get value of given property.
	 * 
	 * @param name name of the property to get
	 * @return the value of the named property, or null if
	 *         the property has not been set
	 */
	public String getProperty(String name) {
		BugProperty prop = lookupProperty(name);
		return prop != null ? prop.getValue() : null;
	}

	/**
	 * Get value of given property, returning given default
	 * value if the property has not been set.
	 * 
	 * @param name         name of the property to get
	 * @param defaultValue default value to return if propery is not set
	 * @return the value of the named property, or the default
	 *         value if the property has not been set
	 */
	public String getProperty(String name, String defaultValue) {
		String value = getProperty(name);
		return value != null ? value : defaultValue;
	}

	/**
	 * Get an Iterator over the properties defined in this BugInstance.
	 * 
	 * @return Iterator over properties
	 */
	public Iterator<BugProperty> propertyIterator() {
		return new BugPropertyIterator();
	}

	/**
	 * Set value of given property.
	 * 
	 * @param name  name of the property to set
	 * @param value the value of the property
	 * @return this object, so calls can be chained
	 */
	public BugInstance setProperty(String name, String value) {
		BugProperty prop = lookupProperty(name);
		if (prop != null) {
			prop.setValue(value);
		} else {
			prop = new BugProperty(name, value);
			addProperty(prop);
		}
		return this;
	}

	/**
	 * Look up a property by name.
	 * 
	 * @param name name of the property to look for
	 * @return the BugProperty with the given name,
	 *         or null if the property has not been set
	 */
	public BugProperty lookupProperty(String name) {
		BugProperty prop = propertyListHead;

		while (prop != null) {
			if (prop.getName().equals(name))
				break;
			prop = prop.getNext();
		}

		return prop;
	}

	/**
	 * Delete property with given name.
	 * 
	 * @param name name of the property to delete
	 * @return true if a property with that name was deleted,
	 *         or false if there is no such property
	 */
	public boolean deleteProperty(String name) {
		BugProperty prev = null;
		BugProperty prop = propertyListHead;

		while (prop != null) {
			if (prop.getName().equals(name))
				break;
			prev = prop;
			prop = prop.getNext();
		}

		if (prop != null) {
			if (prev != null) {
				// Deleted node in interior or at tail of list
				prev.setNext(prop.getNext());
			} else {
				// Deleted node at head of list
				propertyListHead = prop.getNext();
			}

			if (prop.getNext() == null) {
				// Deleted node at end of list
				propertyListTail = prev;
			}

			return true;
		} else {
			// No such property
			return false;
		}
	}

	private void addProperty(BugProperty prop) {
		if (propertyListTail != null) {
			propertyListTail.setNext(prop);
			propertyListTail = prop;
		} else {
			propertyListHead = propertyListTail = prop;
		}
		prop.setNext(null);
	}

	/* ----------------------------------------------------------------------
	 * Generic BugAnnotation adders
	 * ---------------------------------------------------------------------- */

	/**
	 * Add a Collection of BugAnnotations.
	 * 
	 * @param annotationCollection Collection of BugAnnotations
	 */
	public BugInstance addAnnotations(Collection<? extends BugAnnotation> annotationCollection) {
		for (BugAnnotation annotation : annotationCollection) {
			add(annotation);
		}
		return this;
	}

	/* ----------------------------------------------------------------------
	 * Combined annotation adders
	 * ---------------------------------------------------------------------- */

	public BugInstance addClassAndMethod(MethodDescriptor methodDescriptor) {
		addClass(methodDescriptor.getSlashedClassName());
		add(MethodAnnotation.fromMethodDescriptor(methodDescriptor));
		return this;
	}

	/**
	 * Add a class annotation and a method annotation for the class and method
	 * which the given visitor is currently visiting.
	 *
	 * @param visitor the BetterVisitor
	 * @return this object
	 */
	public BugInstance addClassAndMethod(PreorderVisitor visitor) {
		addClass(visitor);
		addMethod(visitor);
		return this;
	}

	/**
	 * Add class and method annotations for given method.
	 *
	 * @param methodAnnotation  the method
	 * @return this object
	 */
	public BugInstance addClassAndMethod(MethodAnnotation methodAnnotation) {
		addClass(methodAnnotation.getClassName());
		addMethod(methodAnnotation);
		return this;
	}

	/**
	 * Add class and method annotations for given method.
	 *
	 * @param methodGen  the method
	 * @param sourceFile source file the method is defined in
	 * @return this object
	 */
	public BugInstance addClassAndMethod(MethodGen methodGen, String sourceFile) {
		addClass(methodGen.getClassName());
		addMethod(methodGen, sourceFile);
		return this;
	}


	/**
	 * Add class and method annotations for given class and method.
	 *  
	 * @param javaClass the class
	 * @param method    the method
	 * @return this object
	 */
	public BugInstance addClassAndMethod(JavaClass javaClass, Method method) {
		addClass(javaClass.getClassName());
		addMethod(javaClass, method);
		return this;
	}

	/* ----------------------------------------------------------------------
	 * Class annotation adders
	 * ---------------------------------------------------------------------- */

	/**
	 * Add a class annotation.  If this is the first class annotation added,
	 * it becomes the primary class annotation.
	 *
	 * @param className the name of the class
	 * @param sourceFileName the source file of the class
	 * @return this object
	 * @deprecated use addClass(String) instead
	 */
	@Deprecated
	public BugInstance addClass(String className, String sourceFileName) {
		ClassAnnotation classAnnotation = new ClassAnnotation(className);
		add(classAnnotation);
		return this;
	}

	/**
	 * Add a class annotation.  If this is the first class annotation added,
	 * it becomes the primary class annotation.
	 *
	 * @param className the name of the class
	 * @return this object
	 */
	public BugInstance addClass(String className) {
		className = ClassName.toDottedClassName(className);
		ClassAnnotation classAnnotation = new ClassAnnotation(className);
		add(classAnnotation);
		return this;
	}

	/**
	 * Add a class annotation for the classNode.
	 *
	 * @param classNode the ASM visitor
	 * @return this object
	 */
	public BugInstance addClass(ClassNode classNode) {
		ClassAnnotation classAnnotation = new ClassAnnotation(classNode.name);
		add(classAnnotation);
		return this;
	}

	/**
	 * Add a class annotation.  If this is the first class annotation added,
	 * it becomes the primary class annotation.
	 * 
	 * @param classDescriptor the class to add
	 * @return this object
	 */
	public BugInstance addClass(ClassDescriptor classDescriptor) {
		add(ClassAnnotation.fromClassDescriptor(classDescriptor));
		return this;
	}

	/**
	 * Add a class annotation.  If this is the first class annotation added,
	 * it becomes the primary class annotation.
	 *
	 * @param jclass the JavaClass object for the class
	 * @return this object
	 */
	public BugInstance addClass(JavaClass jclass) {
		addClass(jclass.getClassName());
		return this;
	}

	/**
	 * Add a class annotation for the class that the visitor is currently visiting.
	 *
	 * @param visitor the BetterVisitor
	 * @return this object
	 */
	public BugInstance addClass(PreorderVisitor visitor) {
		String className = visitor.getDottedClassName();
		addClass(className);
		return this;
	}

	/**
	 * Add a class annotation for the superclass of the class the visitor
	 * is currently visiting.
	 *
	 * @param visitor the BetterVisitor
	 * @return this object
	 */
	public BugInstance addSuperclass(PreorderVisitor visitor) {
		String className = visitor.getSuperclassName();
		addClass(className);
		return this;
	}

	/* ----------------------------------------------------------------------
	 * Type annotation adders
	 * ---------------------------------------------------------------------- */

	/**
	 * Add a type annotation. Handy for referring to array types.
	 *
	 * <p>For information on type descriptors,
	 * <br>see http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#14152
	 * <br>or  http://www.murrayc.com/learning/java/java_classfileformat.shtml#TypeDescriptors
	 * 
	 * @param typeDescriptor a jvm type descriptor, such as "[I"
	 * @return this object
	 */
	public BugInstance addType(String typeDescriptor) {
		TypeAnnotation typeAnnotation = new TypeAnnotation(typeDescriptor);
		add(typeAnnotation);
		return this;
	}

	public BugInstance addFoundAndExpectedType(Type foundType, Type expectedType) {
		
		add( new TypeAnnotation(foundType, TypeAnnotation.FOUND_ROLE));
		add( new TypeAnnotation(expectedType, TypeAnnotation.EXPECTED_ROLE));
		return this;
	}
	
	public BugInstance addFoundAndExpectedType(String foundType, String expectedType) {
		add( new TypeAnnotation(foundType, TypeAnnotation.FOUND_ROLE));
		add( new TypeAnnotation(expectedType, TypeAnnotation.EXPECTED_ROLE));
		return this;
	}
	
	public BugInstance addEqualsMethodUsed(ClassDescriptor expectedClass) {
		try {
			Set<XMethod> targets = Hierarchy2.resolveVirtualMethodCallTargets(expectedClass, "equals", "(Ljava/lang/Object;)Z",
			        false, false);
			addEqualsMethodUsed(targets);

		} catch (ClassNotFoundException e) {
			AnalysisContext.reportMissingClass(e);
		}

		return this;
	}

	public BugInstance addEqualsMethodUsed(@CheckForNull Collection<XMethod> equalsMethods) {
		if (equalsMethods == null) return this;
		if (equalsMethods.size() < 5) {
			for (XMethod m : equalsMethods) {
				addMethod(m).describe(MethodAnnotation.METHOD_EQUALS_USED);
			}
		} else {
			addMethod(equalsMethods.iterator().next()).describe(MethodAnnotation.METHOD_EQUALS_USED);
		}

		return this;
	}
	public BugInstance addTypeOfNamedClass(String typeName) {
		TypeAnnotation typeAnnotation = new TypeAnnotation("L" + typeName.replace('.','/')+";");
		add(typeAnnotation);
		return this;
	}
	public BugInstance addType(ClassDescriptor c) {
		TypeAnnotation typeAnnotation = new TypeAnnotation("L" + c.getClassName()+";");
		add(typeAnnotation);
		return this;
	}
	/* ----------------------------------------------------------------------
	 * Field annotation adders
	 * ---------------------------------------------------------------------- */

	/**
	 * Add a field annotation.
	 *
	 * @param className name of the class containing the field
	 * @param fieldName the name of the field
	 * @param fieldSig  type signature of the field
	 * @param isStatic  whether or not the field is static
	 * @return this object
	 */
	public BugInstance addField(String className, String fieldName, String fieldSig, boolean isStatic) {
		addField(new FieldAnnotation(className, fieldName, fieldSig, isStatic));
		return this;
	}

	/**
	 * Add a field annotation.
	 *
	 * @param className name of the class containing the field
	 * @param fieldName the name of the field
	 * @param fieldSig  type signature of the field
	 * @param accessFlags access flags for the field
	 * @return this object
	 */
	public BugInstance addField(String className, String fieldName, String fieldSig, int accessFlags) {
		addField(new FieldAnnotation(className, fieldName, fieldSig, accessFlags));
		return this;
	}
	public BugInstance addField(PreorderVisitor visitor) {
		FieldAnnotation fieldAnnotation = FieldAnnotation.fromVisitedField(visitor);
		return addField(fieldAnnotation);
	}
	/**
	 * Add a field annotation
	 *
	 * @param fieldAnnotation the field annotation
	 * @return this object
	 */
	public BugInstance addField(FieldAnnotation fieldAnnotation) {
		add(fieldAnnotation);
		return this;
	}

	/**
	 * Add a field annotation for a FieldVariable matched in a ByteCodePattern.
	 *
	 * @param field the FieldVariable
	 * @return this object
	 */
	public BugInstance addField(FieldVariable field) {
		return addField(field.getClassName(), field.getFieldName(), field.getFieldSig(), field.isStatic());
	}

	/**
	 * Add a field annotation for an XField.
	 *
	 * @param xfield the XField
	 * @return this object
	 */
	public BugInstance addOptionalField(@CheckForNull XField xfield) {
		if (xfield == null) return this;
		return addField(xfield.getClassName(), xfield.getName(), xfield.getSignature(), xfield.isStatic());
	}
	/**
	 * Add a field annotation for an XField.
	 *
	 * @param xfield the XField
	 * @return this object
	 */
	public BugInstance addField(XField xfield) {
		return addField(xfield.getClassName(), xfield.getName(), xfield.getSignature(), xfield.isStatic());
	}
	/**
	 * Add a field annotation for a FieldDescriptor.
	 * 
	 * @param fieldDescriptor the FieldDescriptor
	 * @return this object
	 */
	public BugInstance addField(FieldDescriptor fieldDescriptor) {
		FieldAnnotation fieldAnnotation = FieldAnnotation.fromFieldDescriptor(fieldDescriptor);
		add(fieldAnnotation);
		return this;
	}

	/**
	 * Add a field annotation for the field which has just been accessed
	 * by the method currently being visited by given visitor.
	 * Assumes that a getfield/putfield or getstatic/putstatic
	 * has just been seen.
	 *
	 * @param visitor the DismantleBytecode object
	 * @return this object
	 */
	public BugInstance addReferencedField(DismantleBytecode visitor) {
		FieldAnnotation f = FieldAnnotation.fromReferencedField(visitor);
		addField(f);
		return this;
	}

	/**
	 * Add a field annotation for the field referenced by the FieldAnnotation parameter
	 */
	public BugInstance addReferencedField(FieldAnnotation fa) {
		addField(fa);
		return this;
	}

	/**
	 * Add a field annotation for the field which is being visited by
	 * given visitor.
	 *
	 * @param visitor the visitor
	 * @return this object
	 */
	public BugInstance addVisitedField(PreorderVisitor visitor) {
		FieldAnnotation f = FieldAnnotation.fromVisitedField(visitor);
		addField(f);
		return this;
	}

	/**
	 * Local variable adders
	 */
	public BugInstance addOptionalLocalVariable(DismantleBytecode dbc, OpcodeStack.Item item) {
		int register = item.getRegisterNumber();

		if (register >= 0)
			this.add(LocalVariableAnnotation.getLocalVariableAnnotation(dbc.getMethod(), register, dbc.getPC()-1, dbc.getPC()));
		return this;
	}
	
	
	/* ----------------------------------------------------------------------
	 * Method annotation adders
	 * ---------------------------------------------------------------------- */

	/**
	 * Add a method annotation.  If this is the first method annotation added,
	 * it becomes the primary method annotation.
	 *
	 * @param className  name of the class containing the method
	 * @param methodName name of the method
	 * @param methodSig  type signature of the method
	 * @param isStatic   true if the method is static, false otherwise
	 * @return this object
	 */
	public BugInstance addMethod(String className, String methodName, String methodSig, boolean isStatic) {
		addMethod(MethodAnnotation.fromForeignMethod(className, methodName, methodSig, isStatic));
		return this;
	}

	/**
	 * Add a method annotation.  If this is the first method annotation added,
	 * it becomes the primary method annotation.
	 *
	 * @param className  name of the class containing the method
	 * @param methodName name of the method
	 * @param methodSig  type signature of the method
	 * @param accessFlags   accessFlags for the method
	 * @return this object
	 */
	public BugInstance addMethod(String className, String methodName, String methodSig, int accessFlags) {
		addMethod(MethodAnnotation.fromForeignMethod(className, methodName, methodSig, accessFlags));
		return this;
	}

	/**
	 * Add a method annotation.  If this is the first method annotation added,
	 * it becomes the primary method annotation.
	 * If the method has source line information, then a SourceLineAnnotation
	 * is added to the method.
	 *
	 * @param methodGen  the MethodGen object for the method
	 * @param sourceFile source file method is defined in
	 * @return this object
	 */
	public BugInstance addMethod(MethodGen methodGen, String sourceFile) {
		String className = methodGen.getClassName();
		MethodAnnotation methodAnnotation =
				new MethodAnnotation(className, methodGen.getName(), methodGen.getSignature(), methodGen.isStatic());
		addMethod(methodAnnotation);
		addSourceLinesForMethod(methodAnnotation, SourceLineAnnotation.fromVisitedMethod(methodGen, sourceFile));
		return this;
	}

	/**
	 * Add a method annotation.  If this is the first method annotation added,
	 * it becomes the primary method annotation.
	 * If the method has source line information, then a SourceLineAnnotation
	 * is added to the method.
	 *
	 * @param javaClass the class the method is defined in
	 * @param method    the method
	 * @return this object
	 */
	public BugInstance addMethod(JavaClass javaClass, Method method) {
		MethodAnnotation methodAnnotation =
			new MethodAnnotation(javaClass.getClassName(), method.getName(), method.getSignature(), method.isStatic());
		SourceLineAnnotation methodSourceLines = SourceLineAnnotation.forEntireMethod(
				javaClass,
				method);
		methodAnnotation.setSourceLines(methodSourceLines);
		addMethod(methodAnnotation);
		return this;
	}

	/**
	 * Add a method annotation.  If this is the first method annotation added,
	 * it becomes the primary method annotation.
	 * If the method has source line information, then a SourceLineAnnotation
	 * is added to the method.
	 *
	 * @param classAndMethod JavaClassAndMethod identifying the method to add
	 * @return this object
	 */
	public BugInstance addMethod(JavaClassAndMethod classAndMethod) {
		return addMethod(classAndMethod.getJavaClass(), classAndMethod.getMethod());
	}

	/**
	 * Add a method annotation for the method which the given visitor is currently visiting.
	 * If the method has source line information, then a SourceLineAnnotation
	 * is added to the method.
	 *
	 * @param visitor the BetterVisitor
	 * @return this object
	 */
	public BugInstance addMethod(PreorderVisitor visitor) {
		MethodAnnotation methodAnnotation = MethodAnnotation.fromVisitedMethod(visitor);
		addMethod(methodAnnotation);
		addSourceLinesForMethod(methodAnnotation, SourceLineAnnotation.fromVisitedMethod(visitor));
		return this;
	}

	/**
	 * Add a method annotation for the method which has been called
	 * by the method currently being visited by given visitor.
	 * Assumes that the visitor has just looked at an invoke instruction
	 * of some kind.
	 *
	 * @param visitor the DismantleBytecode object
	 * @return this object
	 */
	public BugInstance addCalledMethod(DismantleBytecode visitor) {
		return addMethod(MethodAnnotation.fromCalledMethod(visitor)).describe(MethodAnnotation.METHOD_CALLED);
	}

	/**
	 * Add a method annotation.
	 *
	 * @param className  name of class containing called method
	 * @param methodName name of called method
	 * @param methodSig  signature of called method
	 * @param isStatic   true if called method is static, false if not
	 * @return this object
	 */
	public BugInstance addCalledMethod(String className, String methodName, String methodSig, boolean isStatic) {
		return addMethod(MethodAnnotation.fromCalledMethod(className, methodName, methodSig, isStatic)).describe(MethodAnnotation.METHOD_CALLED);
	}

	/**
	 * Add a method annotation for the method which is called by given
	 * instruction.
	 *
	 * @param methodGen the method containing the call
	 * @param inv       the InvokeInstruction
	 * @return this object
	 */
	public BugInstance addCalledMethod(ConstantPoolGen cpg, InvokeInstruction inv) {
		String className = inv.getClassName(cpg);
		String methodName = inv.getMethodName(cpg);
		String methodSig = inv.getSignature(cpg);
		addMethod(className, methodName, methodSig, inv.getOpcode() == Constants.INVOKESTATIC);
		describe(MethodAnnotation.METHOD_CALLED);
		return this;
	}

	/**
	 * Add a method annotation for the method which is called by given
	 * instruction.
	 *
	 * @param methodGen the method containing the call
	 * @param inv       the InvokeInstruction
	 * @return this object
	 */
	public BugInstance addCalledMethod(MethodGen methodGen, InvokeInstruction inv) {
		ConstantPoolGen cpg = methodGen.getConstantPool();
		return addCalledMethod(cpg, inv);
	}

	/**<commit_msg>Javadoc updates from jjames (Patch 2119512); other updates in patch appear to have been already made.<commit_after>/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2003-2005 University of Maryland
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs;

import java.io.IOException;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.Type;
import org.objectweb.asm.tree.ClassNode;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.Hierarchy2;
import edu.umd.cs.findbugs.ba.JavaClassAndMethod;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.XFactory;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.ba.bcp.FieldVariable;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.FieldDescriptor;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.IAnalysisCache;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;
import edu.umd.cs.findbugs.util.ClassName;
import edu.umd.cs.findbugs.visitclass.DismantleBytecode;
import edu.umd.cs.findbugs.visitclass.PreorderVisitor;
import edu.umd.cs.findbugs.xml.XMLAttributeList;
import edu.umd.cs.findbugs.xml.XMLOutput;

/**
 * An instance of a bug pattern.
 * A BugInstance consists of several parts:
 * <p/>
 * <ul>
 * <li> the type, which is a string indicating what kind of bug it is;
 * used as a key for the FindBugsMessages resource bundle
 * <li> the priority; how likely this instance is to actually be a bug
 * <li> a list of <em>annotations</em>
 * </ul>
 * <p/>
 * The annotations describe classes, methods, fields, source locations,
 * and other relevant context information about the bug instance.
 * Every BugInstance must have at least one ClassAnnotation, which
 * describes the class in which the instance was found.  This is the
 * "primary class annotation".
 * <p/>
 * <p> BugInstance objects are built up by calling a string of <code>add</code>
 * methods.  (These methods all "return this", so they can be chained).
 * Some of the add methods are specialized to get information automatically from
 * a BetterVisitor or DismantleBytecode object.
 *
 * @author <NAME>
 * @see BugAnnotation
 */
public class BugInstance implements Comparable<BugInstance>, XMLWriteableWithMessages, Serializable, Cloneable {
	private static final long serialVersionUID = 1L;

	private String type;
	private int priority;
	private ArrayList<BugAnnotation> annotationList;
	private int cachedHashCode;
	private @CheckForNull  BugDesignation userDesignation;
	private BugProperty propertyListHead, propertyListTail;
	private String uniqueId;
	private String oldInstanceHash;
	private String instanceHash;
	private int instanceOccurrenceNum;
	private int instanceOccurrenceMax;


	/*
	 * The following fields are used for tracking Bug instances across multiple versions of software.
	 * They are meaningless in a BugCollection for just one version of software. 
	 */
	private long firstVersion = 0;
	private long lastVersion = -1;
	private boolean introducedByChangeOfExistingClass;
	private boolean removedByChangeOfPersistingClass;

	/**
	 * This value is used to indicate that the cached hashcode
	 * is invalid, and should be recomputed.
	 */
	private static final int INVALID_HASH_CODE = 0;

	/**
	 * This value is used to indicate whether BugInstances should be reprioritized very low,
	 * when the BugPattern is marked as experimental
	 */
	private static boolean adjustExperimental = false;

	private static Set<String> missingBugTypes = Collections.synchronizedSet(new HashSet<String>());
	
	/**
	 * Constructor.
	 *
	 * @param type     the bug type
	 * @param priority the bug priority
	 */
	public BugInstance(String type, int priority) {
		this.type = type;
		this.priority = priority;
		annotationList = new ArrayList<BugAnnotation>(4);
		cachedHashCode = INVALID_HASH_CODE;

		BugPattern p = I18N.instance().lookupBugPattern(type);
		if (p == null) {
			if (missingBugTypes.add(type)) {
				String msg = "Can't find definition of bug type " + type;
				AnalysisContext.logError(msg, new IllegalArgumentException(msg));
			}
		} else
			this.priority += p.getPriorityAdjustment();
		if (adjustExperimental && isExperimental())
			this.priority = Detector.EXP_PRIORITY;
		boundPriority();
	}

	private void boundPriority() {
		priority = boundedPriority(priority);
	}

	@Override
	public Object clone() {
		BugInstance dup;

		try {
			dup = (BugInstance) super.clone();

			// Do deep copying of mutable objects
			for (int i = 0; i < dup.annotationList.size(); ++i) {
				dup.annotationList.set(i, (BugAnnotation) dup.annotationList.get(i).clone());
			}
			 dup.propertyListHead = dup.propertyListTail = null;
			for (Iterator<BugProperty> i = propertyIterator(); i.hasNext(); ) {
				dup.addProperty((BugProperty) i.next().clone());
			}

			return dup;
		} catch (CloneNotSupportedException e) {
			throw new AssertionError(e);
		}
	}

	/**
	 * Create a new BugInstance.
	 * This is the constructor that should be used by Detectors.
	 * 
	 * @param detector the Detector that is reporting the BugInstance
	 * @param type     the bug type
	 * @param priority the bug priority
	 */
	public BugInstance(Detector detector, String type, int priority) {
		this(type, priority);

		if (detector != null) {
			// Adjust priority if required
			DetectorFactory factory =
				DetectorFactoryCollection.instance().getFactoryByClassName(detector.getClass().getName());
			if (factory != null) {
				this.priority += factory.getPriorityAdjustment();
				boundPriority();
			}
		}

	}

	/**
	 * Create a new BugInstance.
	 * This is the constructor that should be used by Detectors.
	 * 
	 * @param detector the Detector2 that is reporting the BugInstance
	 * @param type     the bug type
	 * @param priority the bug priority
	 */
	public BugInstance(Detector2 detector, String type, int priority) {
		this(type, priority);

		if (detector != null) {
			// Adjust priority if required
			DetectorFactory factory =
				DetectorFactoryCollection.instance().getFactoryByClassName(detector.getDetectorClassName());
			if (factory != null) {
				this.priority += factory.getPriorityAdjustment();
				boundPriority();
			}
		}

	}
	public static void setAdjustExperimental(boolean adjust) {
		adjustExperimental = adjust;
	}

	/* ----------------------------------------------------------------------
	 * Accessors
	 * ---------------------------------------------------------------------- */

	/**
	 * Get the bug type.
	 */
	public String getType() {
		return type;
	}

	/**
	 * Get the BugPattern.
	 */
	public @NonNull BugPattern getBugPattern() {
		BugPattern result =  I18N.instance().lookupBugPattern(getType());
		if (result != null) return result;
		AnalysisContext.logError("Unable to find description of bug pattern " + getType());
		result = I18N.instance().lookupBugPattern("UNKNOWN");
		if (result != null) return result;
		return BugPattern.REALLY_UNKNOWN;
	}

	/**
	 * Get the bug priority.
	 */
	public int getPriority() {
		return priority;
	}

	/**
	 * Get a string describing the bug priority and type.
	 * e.g. "High Priority Correctness"
	 * @return a string describing the bug priority and type
	 */
	public String getPriorityTypeString()
	{
		String priorityString = getPriorityString();
		BugPattern bugPattern = this.getBugPattern();
		//then get the category and put everything together
		String categoryString;
		if (bugPattern == null) categoryString = "Unknown category for " + getType();
		else categoryString = I18N.instance().getBugCategoryDescription(bugPattern.getCategory());
		return priorityString + " Priority " + categoryString;
		//TODO: internationalize the word "Priority"
	}

	public String getPriorityTypeAbbreviation()
	{
		String priorityString = getPriorityAbbreviation();
		return priorityString + " " + getCategoryAbbrev();
	}

	public String getCategoryAbbrev() {
	    BugPattern bugPattern = getBugPattern();
	    if (bugPattern == null) return "?";
		return bugPattern.getCategoryAbbrev();
    }

	public String getPriorityString() {
		//first, get the priority
		int value = this.getPriority();
		String priorityString;
		if (value == Detector.HIGH_PRIORITY)
			priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_high", "High");
		else if (value == Detector.NORMAL_PRIORITY)
			priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_normal", "Medium");
		else if (value == Detector.LOW_PRIORITY)
			priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_low", "Low");
		else if (value == Detector.EXP_PRIORITY)
			priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_experimental", "Experimental");
		else
			priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_ignore", "Ignore"); // This probably shouldn't ever happen, but what the hell, let's be complete
		return priorityString;
	}

	public String getPriorityAbbreviation() {
		return getPriorityString().substring(0,1);
	}
	/**
	 * Set the bug priority.
	 */
	public void setPriority(int p) {
		priority = boundedPriority(p);
	}

	private int boundedPriority(int p) {
		return Math.max(Detector.HIGH_PRIORITY, Math.min(Detector.IGNORE_PRIORITY, p));
	}
	public void raisePriority() {
		priority = boundedPriority(priority-1);

	}
	public void lowerPriority() {
		priority = boundedPriority(priority+1);
	}

	public void lowerPriorityALot() {
		priority = boundedPriority(priority+2);
	}

	/**
	 * Is this bug instance the result of an experimental detector?
	 */
	public boolean isExperimental() {
		BugPattern pattern = getBugPattern();
		return (pattern != null) && pattern.isExperimental();
	}

	/**
	 * Get the primary class annotation, which indicates where the bug occurs.
	 */
	public ClassAnnotation getPrimaryClass() {
		return  findAnnotationOfType(ClassAnnotation.class);
	}

	/**
	 * Get the primary method annotation, which indicates where the bug occurs.
	 */
	public MethodAnnotation getPrimaryMethod() {
		return  findAnnotationOfType(MethodAnnotation.class);
	}
	/**
	 * Get the primary method annotation, which indicates where the bug occurs.
	 */
	public FieldAnnotation getPrimaryField() {
		return findAnnotationOfType(FieldAnnotation.class);
	}


	public BugInstance lowerPriorityIfDeprecated() {
		MethodAnnotation m = getPrimaryMethod();
		if (m != null && XFactory.createXMethod(m).isDeprecated())
				lowerPriority();
		FieldAnnotation f = getPrimaryField();
		if (f != null && XFactory.createXField(f).isDeprecated())
			lowerPriority();
		return this;
	}
	/**
	 * Find the first BugAnnotation in the list of annotations
	 * that is the same type or a subtype as the given Class parameter.
	 * 
	 * @param cls the Class parameter
	 * @return the first matching BugAnnotation of the given type,
	 *         or null if there is no such BugAnnotation
	 */
	private <T extends BugAnnotation> T findAnnotationOfType(Class<T>  cls) {
		for (Iterator<BugAnnotation> i = annotationIterator(); i.hasNext();) {
			BugAnnotation annotation = i.next();
			if (cls.isAssignableFrom(annotation.getClass()))
				return cls.cast(annotation);
		}
		return null;
	}

	public LocalVariableAnnotation getPrimaryLocalVariableAnnotation() {
		for (BugAnnotation annotation : annotationList) 
			if (annotation instanceof LocalVariableAnnotation)
				return (LocalVariableAnnotation) annotation;
		return null;
	}
	/**
	 * Get the primary source line annotation.
	 * There is guaranteed to be one (unless some Detector constructed
	 * an invalid BugInstance).
	 *
	 * @return the source line annotation
	 */
	public SourceLineAnnotation getPrimarySourceLineAnnotation() {
		// Highest priority: return the first top level source line annotation
		for (BugAnnotation annotation : annotationList) {
			if (annotation instanceof SourceLineAnnotation
					&& annotation.getDescription().equals(SourceLineAnnotation.DEFAULT_ROLE))
				return (SourceLineAnnotation) annotation;
		}
		for (BugAnnotation annotation : annotationList) {
			if (annotation instanceof SourceLineAnnotation)
				return (SourceLineAnnotation) annotation;
		}

		// Next: Try primary method, primary field, primary class
		SourceLineAnnotation srcLine;
		if ((srcLine = inspectPackageMemberSourceLines(getPrimaryMethod())) != null)
			return srcLine;
		if ((srcLine = inspectPackageMemberSourceLines(getPrimaryField())) != null)
			return srcLine;
		if ((srcLine = inspectPackageMemberSourceLines(getPrimaryClass())) != null)
			return srcLine;

		// Last resort: throw exception
		throw new IllegalStateException("BugInstance must contain at least one class, method, or field annotation");
	}

	public String getInstanceKey() {
		StringBuilder buf = new StringBuilder(type);
		for (BugAnnotation annotation : annotationList) {
			if (annotation instanceof SourceLineAnnotation || annotation instanceof MethodAnnotation && !annotation.isSignificant()) {
				// do nothing
			} else {
				buf.append(":");
				buf.append(annotation.format("hash", null));
			}
		}
		return buf.toString();
	}
	/**
	 * If given PackageMemberAnnotation is non-null, return its
	 * SourceLineAnnotation.
	 * 
	 * @param packageMember
	 *            a PackageMemberAnnotation
	 * @return the PackageMemberAnnotation's SourceLineAnnotation, or null if
	 *         there is no SourceLineAnnotation
	 */
	private SourceLineAnnotation inspectPackageMemberSourceLines(PackageMemberAnnotation packageMember) {
		return (packageMember != null) ? packageMember.getSourceLines() : null;
	}

	/**
	 * Get an Iterator over all bug annotations.
	 */
	public Iterator<BugAnnotation> annotationIterator() {
		return annotationList.iterator();
	}
	
	/**
	 * Get an Iterator over all bug annotations.
	 */
	public List<? extends BugAnnotation> getAnnotations() {
		return annotationList;
	}

	/**
	 * Get the abbreviation of this bug instance's BugPattern.
	 * This is the same abbreviation used by the BugCode which
	 * the BugPattern is a particular species of.
	 */
	public String getAbbrev() {
		BugPattern pattern = getBugPattern();
		return pattern != null ? pattern.getAbbrev() : "<unknown bug pattern>";
	}

	/** set the user designation object. This will clobber any
	 *  existing annotationText (or any other BugDesignation field). */
	public void setUserDesignation(BugDesignation bd) {
		userDesignation = bd;
	}
	/** return the user designation object, which may be null.
	 * 
	 *  A previous calls to getSafeUserDesignation(), setAnnotationText(),
	 *  or setUserDesignation() will ensure it will be non-null
	 *  [barring an intervening setUserDesignation(null)].
	 *  @see #getNonnullUserDesignation() */
	@Nullable public BugDesignation getUserDesignation() {
		return userDesignation;
	}
	/** return the user designation object, creating one if
	 *  necessary. So calling
	 *  <code>getSafeUserDesignation().setDesignation("HARMLESS")</code>
	 *  will always work without the possibility of a NullPointerException.
	 *  @see #getUserDesignation() */
	@NonNull public BugDesignation getNonnullUserDesignation() {
		if (userDesignation == null)
			userDesignation = new BugDesignation();
		return userDesignation;
	}


	/** Get the user designation key.
	 *  E.g., "MOSTLY_HARMLESS", "CRITICAL", "NOT_A_BUG", etc.
	 *
	 *  If the user designation object is null,returns UNCLASSIFIED.
	 *
	 *  To set the user designation key, call
	 *  <code>getSafeUserDesignation().setDesignation("HARMLESS")</code>.
	 * 
	 *  @see I18N#getUserDesignation(String key)
	 *  @return the user designation key
	 */
	@NonNull public String getUserDesignationKey() {
		BugDesignation userDesignation = this.userDesignation;
		if (userDesignation == null) return BugDesignation.UNCLASSIFIED;
		return userDesignation.getDesignationKey();
	}

	/**
	 * Set the user annotation text.
	 *
	 * @param annotationText the user annotation text
	 */
	public void setAnnotationText(String annotationText) {
		getNonnullUserDesignation().setAnnotationText(annotationText);
	}

	/**
	 * Get the user annotation text.
	 *
	 * @return the user annotation text
	 */
	@NonNull public String getAnnotationText() {
		BugDesignation userDesignation = this.userDesignation;
		if (userDesignation == null) return "";		
		String s = userDesignation.getAnnotationText();
		if (s == null) return "";
		return s;
	}

	/**
	 * Determine whether or not the annotation text contains
	 * the given word.
	 *
	 * @param word the word
	 * @return true if the annotation text contains the word, false otherwise
	 */
	public boolean annotationTextContainsWord(String word) {
		return getTextAnnotationWords().contains(word);
	}

	/**
	 * Get set of words in the text annotation.
	 */
	public Set<String> getTextAnnotationWords() {
		HashSet<String> result = new HashSet<String>();

		StringTokenizer tok = new StringTokenizer(getAnnotationText(), " \t\r\n\f.,:;-");
		while (tok.hasMoreTokens()) {
			result.add(tok.nextToken());
		}
		return result;
	}

	/**
	 * Get the BugInstance's unique id.
	 * 
	 * @return the unique id, or null if no unique id has been assigned
	 * 
	 * Deprecated, since it isn't persistent
	 */
	@Deprecated
	public String getUniqueId() {
		return uniqueId;
	}

	/**
	 * Set the unique id of the BugInstance.
	 * 
	 * @param uniqueId the unique id
	 * 
	 *   * Deprecated, since it isn't persistent
	 */
	@Deprecated
	 void setUniqueId(String uniqueId) {
		this.uniqueId = uniqueId;
	}



	/* ----------------------------------------------------------------------
	 * Property accessors
	 * ---------------------------------------------------------------------- */

	private class BugPropertyIterator implements Iterator<BugProperty> {
		private BugProperty prev, cur;
		private boolean removed;

		/* (non-Javadoc)
		 * @see java.util.Iterator#hasNext()
		 */
		public boolean hasNext() {
			return findNext() != null;
		}
		/* (non-Javadoc)
		 * @see java.util.Iterator#next()
		 */
		public BugProperty next() {
			BugProperty next = findNext();
			if (next == null)
				throw new NoSuchElementException();
			prev = cur;
			cur = next;
			removed = false;
			return cur;
		}

		/* (non-Javadoc)
		 * @see java.util.Iterator#remove()
		 */
		public void remove() {
			if (cur == null || removed)
				throw new IllegalStateException();
			if (prev == null) {
				propertyListHead = cur.getNext();
			} else {
				prev.setNext(cur.getNext());
			}
			if (cur == propertyListTail) {
				propertyListTail = prev;
			}
			removed = true;
		}

		private BugProperty findNext() {
			return cur == null ? propertyListHead : cur.getNext();
		}

	}

	/**
	 * Get value of given property.
	 * 
	 * @param name name of the property to get
	 * @return the value of the named property, or null if
	 *         the property has not been set
	 */
	public String getProperty(String name) {
		BugProperty prop = lookupProperty(name);
		return prop != null ? prop.getValue() : null;
	}

	/**
	 * Get value of given property, returning given default
	 * value if the property has not been set.
	 * 
	 * @param name         name of the property to get
	 * @param defaultValue default value to return if propery is not set
	 * @return the value of the named property, or the default
	 *         value if the property has not been set
	 */
	public String getProperty(String name, String defaultValue) {
		String value = getProperty(name);
		return value != null ? value : defaultValue;
	}

	/**
	 * Get an Iterator over the properties defined in this BugInstance.
	 * 
	 * @return Iterator over properties
	 */
	public Iterator<BugProperty> propertyIterator() {
		return new BugPropertyIterator();
	}

	/**
	 * Set value of given property.
	 * 
	 * @param name  name of the property to set
	 * @param value the value of the property
	 * @return this object, so calls can be chained
	 */
	public BugInstance setProperty(String name, String value) {
		BugProperty prop = lookupProperty(name);
		if (prop != null) {
			prop.setValue(value);
		} else {
			prop = new BugProperty(name, value);
			addProperty(prop);
		}
		return this;
	}

	/**
	 * Look up a property by name.
	 * 
	 * @param name name of the property to look for
	 * @return the BugProperty with the given name,
	 *         or null if the property has not been set
	 */
	public BugProperty lookupProperty(String name) {
		BugProperty prop = propertyListHead;

		while (prop != null) {
			if (prop.getName().equals(name))
				break;
			prop = prop.getNext();
		}

		return prop;
	}

	/**
	 * Delete property with given name.
	 * 
	 * @param name name of the property to delete
	 * @return true if a property with that name was deleted,
	 *         or false if there is no such property
	 */
	public boolean deleteProperty(String name) {
		BugProperty prev = null;
		BugProperty prop = propertyListHead;

		while (prop != null) {
			if (prop.getName().equals(name))
				break;
			prev = prop;
			prop = prop.getNext();
		}

		if (prop != null) {
			if (prev != null) {
				// Deleted node in interior or at tail of list
				prev.setNext(prop.getNext());
			} else {
				// Deleted node at head of list
				propertyListHead = prop.getNext();
			}

			if (prop.getNext() == null) {
				// Deleted node at end of list
				propertyListTail = prev;
			}

			return true;
		} else {
			// No such property
			return false;
		}
	}

	private void addProperty(BugProperty prop) {
		if (propertyListTail != null) {
			propertyListTail.setNext(prop);
			propertyListTail = prop;
		} else {
			propertyListHead = propertyListTail = prop;
		}
		prop.setNext(null);
	}

	/* ----------------------------------------------------------------------
	 * Generic BugAnnotation adders
	 * ---------------------------------------------------------------------- */

	/**
	 * Add a Collection of BugAnnotations.
	 * 
	 * @param annotationCollection Collection of BugAnnotations
	 */
	public BugInstance addAnnotations(Collection<? extends BugAnnotation> annotationCollection) {
		for (BugAnnotation annotation : annotationCollection) {
			add(annotation);
		}
		return this;
	}

	/* ----------------------------------------------------------------------
	 * Combined annotation adders
	 * ---------------------------------------------------------------------- */

	public BugInstance addClassAndMethod(MethodDescriptor methodDescriptor) {
		addClass(methodDescriptor.getSlashedClassName());
		add(MethodAnnotation.fromMethodDescriptor(methodDescriptor));
		return this;
	}

	/**
	 * Add a class annotation and a method annotation for the class and method
	 * which the given visitor is currently visiting.
	 *
	 * @param visitor the BetterVisitor
	 * @return this object
	 */
	public BugInstance addClassAndMethod(PreorderVisitor visitor) {
		addClass(visitor);
		addMethod(visitor);
		return this;
	}

	/**
	 * Add class and method annotations for given method.
	 *
	 * @param methodAnnotation  the method
	 * @return this object
	 */
	public BugInstance addClassAndMethod(MethodAnnotation methodAnnotation) {
		addClass(methodAnnotation.getClassName());
		addMethod(methodAnnotation);
		return this;
	}

	/**
	 * Add class and method annotations for given method.
	 *
	 * @param methodGen  the method
	 * @param sourceFile source file the method is defined in
	 * @return this object
	 */
	public BugInstance addClassAndMethod(MethodGen methodGen, String sourceFile) {
		addClass(methodGen.getClassName());
		addMethod(methodGen, sourceFile);
		return this;
	}


	/**
	 * Add class and method annotations for given class and method.
	 *  
	 * @param javaClass the class
	 * @param method    the method
	 * @return this object
	 */
	public BugInstance addClassAndMethod(JavaClass javaClass, Method method) {
		addClass(javaClass.getClassName());
		addMethod(javaClass, method);
		return this;
	}

	/* ----------------------------------------------------------------------
	 * Class annotation adders
	 * ---------------------------------------------------------------------- */

	/**
	 * Add a class annotation.  If this is the first class annotation added,
	 * it becomes the primary class annotation.
	 *
	 * @param className the name of the class
	 * @param sourceFileName the source file of the class
	 * @return this object
	 * @deprecated use addClass(String) instead
	 */
	@Deprecated
	public BugInstance addClass(String className, String sourceFileName) {
		ClassAnnotation classAnnotation = new ClassAnnotation(className);
		add(classAnnotation);
		return this;
	}

	/**
	 * Add a class annotation.  If this is the first class annotation added,
	 * it becomes the primary class annotation.
	 *
	 * @param className the name of the class
	 * @return this object
	 */
	public BugInstance addClass(String className) {
		className = ClassName.toDottedClassName(className);
		ClassAnnotation classAnnotation = new ClassAnnotation(className);
		add(classAnnotation);
		return this;
	}

	/**
	 * Add a class annotation for the classNode.
	 *
	 * @param classNode the ASM visitor
	 * @return this object
	 */
	public BugInstance addClass(ClassNode classNode) {
		ClassAnnotation classAnnotation = new ClassAnnotation(classNode.name);
		add(classAnnotation);
		return this;
	}

	/**
	 * Add a class annotation.  If this is the first class annotation added,
	 * it becomes the primary class annotation.
	 * 
	 * @param classDescriptor the class to add
	 * @return this object
	 */
	public BugInstance addClass(ClassDescriptor classDescriptor) {
		add(ClassAnnotation.fromClassDescriptor(classDescriptor));
		return this;
	}

	/**
	 * Add a class annotation.  If this is the first class annotation added,
	 * it becomes the primary class annotation.
	 *
	 * @param jclass the JavaClass object for the class
	 * @return this object
	 */
	public BugInstance addClass(JavaClass jclass) {
		addClass(jclass.getClassName());
		return this;
	}

	/**
	 * Add a class annotation for the class that the visitor is currently visiting.
	 *
	 * @param visitor the BetterVisitor
	 * @return this object
	 */
	public BugInstance addClass(PreorderVisitor visitor) {
		String className = visitor.getDottedClassName();
		addClass(className);
		return this;
	}

	/**
	 * Add a class annotation for the superclass of the class the visitor
	 * is currently visiting.
	 *
	 * @param visitor the BetterVisitor
	 * @return this object
	 */
	public BugInstance addSuperclass(PreorderVisitor visitor) {
		String className = visitor.getSuperclassName();
		addClass(className);
		return this;
	}

	/* ----------------------------------------------------------------------
	 * Type annotation adders
	 * ---------------------------------------------------------------------- */

	/**
	 * Add a type annotation. Handy for referring to array types.
	 *
	 * <p>For information on type descriptors,
	 * <br>see http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#14152
	 * <br>or  http://www.murrayc.com/learning/java/java_classfileformat.shtml#TypeDescriptors
	 * 
	 * @param typeDescriptor a jvm type descriptor, such as "[I"
	 * @return this object
	 */
	public BugInstance addType(String typeDescriptor) {
		TypeAnnotation typeAnnotation = new TypeAnnotation(typeDescriptor);
		add(typeAnnotation);
		return this;
	}

	public BugInstance addFoundAndExpectedType(Type foundType, Type expectedType) {
		
		add( new TypeAnnotation(foundType, TypeAnnotation.FOUND_ROLE));
		add( new TypeAnnotation(expectedType, TypeAnnotation.EXPECTED_ROLE));
		return this;
	}
	
	public BugInstance addFoundAndExpectedType(String foundType, String expectedType) {
		add( new TypeAnnotation(foundType, TypeAnnotation.FOUND_ROLE));
		add( new TypeAnnotation(expectedType, TypeAnnotation.EXPECTED_ROLE));
		return this;
	}
	
	public BugInstance addEqualsMethodUsed(ClassDescriptor expectedClass) {
		try {
			Set<XMethod> targets = Hierarchy2.resolveVirtualMethodCallTargets(expectedClass, "equals", "(Ljava/lang/Object;)Z",
			        false, false);
			addEqualsMethodUsed(targets);

		} catch (ClassNotFoundException e) {
			AnalysisContext.reportMissingClass(e);
		}

		return this;
	}

	public BugInstance addEqualsMethodUsed(@CheckForNull Collection<XMethod> equalsMethods) {
		if (equalsMethods == null) return this;
		if (equalsMethods.size() < 5) {
			for (XMethod m : equalsMethods) {
				addMethod(m).describe(MethodAnnotation.METHOD_EQUALS_USED);
			}
		} else {
			addMethod(equalsMethods.iterator().next()).describe(MethodAnnotation.METHOD_EQUALS_USED);
		}

		return this;
	}
	public BugInstance addTypeOfNamedClass(String typeName) {
		TypeAnnotation typeAnnotation = new TypeAnnotation("L" + typeName.replace('.','/')+";");
		add(typeAnnotation);
		return this;
	}
	public BugInstance addType(ClassDescriptor c) {
		TypeAnnotation typeAnnotation = new TypeAnnotation("L" + c.getClassName()+";");
		add(typeAnnotation);
		return this;
	}
	/* ----------------------------------------------------------------------
	 * Field annotation adders
	 * ---------------------------------------------------------------------- */

	/**
	 * Add a field annotation.
	 *
	 * @param className name of the class containing the field
	 * @param fieldName the name of the field
	 * @param fieldSig  type signature of the field
	 * @param isStatic  whether or not the field is static
	 * @return this object
	 */
	public BugInstance addField(String className, String fieldName, String fieldSig, boolean isStatic) {
		addField(new FieldAnnotation(className, fieldName, fieldSig, isStatic));
		return this;
	}

	/**
	 * Add a field annotation.
	 *
	 * @param className name of the class containing the field
	 * @param fieldName the name of the field
	 * @param fieldSig  type signature of the field
	 * @param accessFlags access flags for the field
	 * @return this object
	 */
	public BugInstance addField(String className, String fieldName, String fieldSig, int accessFlags) {
		addField(new FieldAnnotation(className, fieldName, fieldSig, accessFlags));
		return this;
	}
	public BugInstance addField(PreorderVisitor visitor) {
		FieldAnnotation fieldAnnotation = FieldAnnotation.fromVisitedField(visitor);
		return addField(fieldAnnotation);
	}
	/**
	 * Add a field annotation
	 *
	 * @param fieldAnnotation the field annotation
	 * @return this object
	 */
	public BugInstance addField(FieldAnnotation fieldAnnotation) {
		add(fieldAnnotation);
		return this;
	}

	/**
	 * Add a field annotation for a FieldVariable matched in a ByteCodePattern.
	 *
	 * @param field the FieldVariable
	 * @return this object
	 */
	public BugInstance addField(FieldVariable field) {
		return addField(field.getClassName(), field.getFieldName(), field.getFieldSig(), field.isStatic());
	}

	/**
	 * Add a field annotation for an XField.
	 *
	 * @param xfield the XField
	 * @return this object
	 */
	public BugInstance addOptionalField(@CheckForNull XField xfield) {
		if (xfield == null) return this;
		return addField(xfield.getClassName(), xfield.getName(), xfield.getSignature(), xfield.isStatic());
	}
	/**
	 * Add a field annotation for an XField.
	 *
	 * @param xfield the XField
	 * @return this object
	 */
	public BugInstance addField(XField xfield) {
		return addField(xfield.getClassName(), xfield.getName(), xfield.getSignature(), xfield.isStatic());
	}
	/**
	 * Add a field annotation for a FieldDescriptor.
	 * 
	 * @param fieldDescriptor the FieldDescriptor
	 * @return this object
	 */
	public BugInstance addField(FieldDescriptor fieldDescriptor) {
		FieldAnnotation fieldAnnotation = FieldAnnotation.fromFieldDescriptor(fieldDescriptor);
		add(fieldAnnotation);
		return this;
	}

	/**
	 * Add a field annotation for the field which has just been accessed
	 * by the method currently being visited by given visitor.
	 * Assumes that a getfield/putfield or getstatic/putstatic
	 * has just been seen.
	 *
	 * @param visitor the DismantleBytecode object
	 * @return this object
	 */
	public BugInstance addReferencedField(DismantleBytecode visitor) {
		FieldAnnotation f = FieldAnnotation.fromReferencedField(visitor);
		addField(f);
		return this;
	}

	/**
	 * Add a field annotation for the field referenced by the FieldAnnotation parameter
	 */
	public BugInstance addReferencedField(FieldAnnotation fa) {
		addField(fa);
		return this;
	}

	/**
	 * Add a field annotation for the field which is being visited by
	 * given visitor.
	 *
	 * @param visitor the visitor
	 * @return this object
	 */
	public BugInstance addVisitedField(PreorderVisitor visitor) {
		FieldAnnotation f = FieldAnnotation.fromVisitedField(visitor);
		addField(f);
		return this;
	}

	/**
	 * Local variable adders
	 */
	public BugInstance addOptionalLocalVariable(DismantleBytecode dbc, OpcodeStack.Item item) {
		int register = item.getRegisterNumber();

		if (register >= 0)
			this.add(LocalVariableAnnotation.getLocalVariableAnnotation(dbc.getMethod(), register, dbc.getPC()-1, dbc.getPC()));
		return this;
	}
	
	
	/* ----------------------------------------------------------------------
	 * Method annotation adders
	 * ---------------------------------------------------------------------- */

	/**
	 * Add a method annotation.  If this is the first method annotation added,
	 * it becomes the primary method annotation.
	 *
	 * @param className  name of the class containing the method
	 * @param methodName name of the method
	 * @param methodSig  type signature of the method
	 * @param isStatic   true if the method is static, false otherwise
	 * @return this object
	 */
	public BugInstance addMethod(String className, String methodName, String methodSig, boolean isStatic) {
		addMethod(MethodAnnotation.fromForeignMethod(className, methodName, methodSig, isStatic));
		return this;
	}

	/**
	 * Add a method annotation.  If this is the first method annotation added,
	 * it becomes the primary method annotation.
	 *
	 * @param className  name of the class containing the method
	 * @param methodName name of the method
	 * @param methodSig  type signature of the method
	 * @param accessFlags   accessFlags for the method
	 * @return this object
	 */
	public BugInstance addMethod(String className, String methodName, String methodSig, int accessFlags) {
		addMethod(MethodAnnotation.fromForeignMethod(className, methodName, methodSig, accessFlags));
		return this;
	}

	/**
	 * Add a method annotation.  If this is the first method annotation added,
	 * it becomes the primary method annotation.
	 * If the method has source line information, then a SourceLineAnnotation
	 * is added to the method.
	 *
	 * @param methodGen  the MethodGen object for the method
	 * @param sourceFile source file method is defined in
	 * @return this object
	 */
	public BugInstance addMethod(MethodGen methodGen, String sourceFile) {
		String className = methodGen.getClassName();
		MethodAnnotation methodAnnotation =
				new MethodAnnotation(className, methodGen.getName(), methodGen.getSignature(), methodGen.isStatic());
		addMethod(methodAnnotation);
		addSourceLinesForMethod(methodAnnotation, SourceLineAnnotation.fromVisitedMethod(methodGen, sourceFile));
		return this;
	}

	/**
	 * Add a method annotation.  If this is the first method annotation added,
	 * it becomes the primary method annotation.
	 * If the method has source line information, then a SourceLineAnnotation
	 * is added to the method.
	 *
	 * @param javaClass the class the method is defined in
	 * @param method    the method
	 * @return this object
	 */
	public BugInstance addMethod(JavaClass javaClass, Method method) {
		MethodAnnotation methodAnnotation =
			new MethodAnnotation(javaClass.getClassName(), method.getName(), method.getSignature(), method.isStatic());
		SourceLineAnnotation methodSourceLines = SourceLineAnnotation.forEntireMethod(
				javaClass,
				method);
		methodAnnotation.setSourceLines(methodSourceLines);
		addMethod(methodAnnotation);
		return this;
	}

	/**
	 * Add a method annotation.  If this is the first method annotation added,
	 * it becomes the primary method annotation.
	 * If the method has source line information, then a SourceLineAnnotation
	 * is added to the method.
	 *
	 * @param classAndMethod JavaClassAndMethod identifying the method to add
	 * @return this object
	 */
	public BugInstance addMethod(JavaClassAndMethod classAndMethod) {
		return addMethod(classAndMethod.getJavaClass(), classAndMethod.getMethod());
	}

	/**
	 * Add a method annotation for the method which the given visitor is currently visiting.
	 * If the method has source line information, then a SourceLineAnnotation
	 * is added to the method.
	 *
	 * @param visitor the BetterVisitor
	 * @return this object
	 */
	public BugInstance addMethod(PreorderVisitor visitor) {
		MethodAnnotation methodAnnotation = MethodAnnotation.fromVisitedMethod(visitor);
		addMethod(methodAnnotation);
		addSourceLinesForMethod(methodAnnotation, SourceLineAnnotation.fromVisitedMethod(visitor));
		return this;
	}

	/**
	 * Add a method annotation for the method which has been called
	 * by the method currently being visited by given visitor.
	 * Assumes that the visitor has just looked at an invoke instruction
	 * of some kind.
	 *
	 * @param visitor the DismantleBytecode object
	 * @return this object
	 */
	public BugInstance addCalledMethod(DismantleBytecode visitor) {
		return addMethod(MethodAnnotation.fromCalledMethod(visitor)).describe(MethodAnnotation.METHOD_CALLED);
	}

	/**
	 * Add a method annotation.
	 *
	 * @param className  name of class containing called method
	 * @param methodName name of called method
	 * @param methodSig  signature of called method
	 * @param isStatic   true if called method is static, false if not
	 * @return this object
	 */
	public BugInstance addCalledMethod(String className, String methodName, String methodSig, boolean isStatic) {
		return addMethod(MethodAnnotation.fromCalledMethod(className, methodName, methodSig, isStatic)).describe(MethodAnnotation.METHOD_CALLED);
	}

	/**
	 * Add a method annotation for the method which is called by given
	 * instruction.
	 *
	 * @param cpg the constant pool for the method containing the call
	 * @param inv the InvokeInstruction
	 * @return this object
	 */
	public BugInstance addCalledMethod(ConstantPoolGen cpg, InvokeInstruction inv) {
		String className = inv.getClassName(cpg);
		String methodName = inv.getMethodName(cpg);
		String methodSig = inv.getSignature(cpg);
		addMethod(className, methodName, methodSig, inv.getOpcode() == Constants.INVOKESTATIC);
		describe(MethodAnnotation.METHOD_CALLED);
		return this;
	}

	/**
	 * Add a method annotation for the method which is called by given
	 * instruction.
	 *
	 * @param methodGen the method containing the call
	 * @param inv       the InvokeInstruction
	 * @return this object
	 */
	public BugInstance addCalledMethod(MethodGen methodGen, InvokeInstruction inv) {
		ConstantPoolGen cpg = methodGen.getConstantPool();
		return addCalledMethod(cpg, inv);
	}

	/**
{'fraction_non_alphanumeric': 0.11914767390122113, 'fraction_numerical': 0.0017674395609828448, 'mean_word_length': 3.541112420721783, 'pattern_counts': {'":': 2, '<': 89, '<?xml version=': 0, '>': 87, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '9667', 'n_tokens_mistral': 24377, 'n_tokens_neox': 21632, 'n_words': 7155}