/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2003,2004 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.StringWriter;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.xml.transform.TransformerException;

/**
 * An implementation of {@link BugCollection} that keeps the BugInstances
 * sorted by class (using the native comparison ordering of BugInstance's
 * compareTo() method as a tie-breaker).
 *
 * @see BugInstance
 * @author David Hovemeyer
 */
public class SortedBugCollection extends BugCollection {
	private static class BugInstanceComparator implements Comparator<BugInstance> {
		public int compare(BugInstance lhs, BugInstance rhs) {
			ClassAnnotation lca = lhs.getPrimaryClass();
			ClassAnnotation rca = rhs.getPrimaryClass();
			if (lca == null || rca == null)
				throw new IllegalStateException("null class annotation: " + lca + "," + rca);
			int cmp = lca.getClassName().compareTo(rca.getClassName());
			if (cmp != 0)
				return cmp;
			return lhs.compareTo(rhs);
		}
	}

	private static final BugInstanceComparator comparator = new BugInstanceComparator();

	private TreeSet<BugInstance> bugSet;
	private List<AnalysisError> errorList;
	private TreeSet<String> missingClassSet;
	private String summaryHTML;
	private ProjectStats projectStats;

	private Map<String, BugInstance> uniqueIdToBugInstanceMap;
	private int generatedUniqueIdCount;
	private long timestamp;

	/**
	 * Constructor.
	 * Creates an empty object.
	 */
	public SortedBugCollection() {
		this(new ProjectStats());
	}
	
	/**
	 * Constructor.
	 * Creates an empty object given an existing ProjectStats.
	 * 
	 * @param projectStats the ProjectStats
	 */
	public SortedBugCollection(ProjectStats projectStats) {
		this.projectStats = projectStats;
		bugSet = new TreeSet<BugInstance>(comparator);
		bugSet = new TreeSet<BugInstance>(comparator);
		errorList = new LinkedList<AnalysisError>();
		missingClassSet = new TreeSet<String>();
		summaryHTML = "";
		uniqueIdToBugInstanceMap = new HashMap<String, BugInstance>();
		generatedUniqueIdCount = 0;
		timestamp = System.currentTimeMillis();
	}

	public boolean add(BugInstance bugInstance) {
		registerUniqueId(bugInstance);

		// Mark the BugInstance as being active at the BugCollection's current timestamp.
		TimestampIntervalCollection activeIntervalCollection =
			bugInstance.getActiveIntervalCollection();
		activeIntervalCollection.add(new TimestampInterval(timestamp, timestamp));
		bugInstance.setActiveIntervalCollection(activeIntervalCollection);
		
		return bugSet.add(bugInstance);
	}

	/**
	 * Create a unique id for a BugInstance if it doesn't already have one,
	 * or if the unique id it has conflicts with a BugInstance that is
	 * already in the collection.
	 * 
	 * @param bugInstance the BugInstance
	 */
	private void registerUniqueId(BugInstance bugInstance) {
		// If the BugInstance has no unique id, generate one.
		// If the BugInstance has a unique id which conflicts with
		// an existing BugInstance, then we also generate a new
		// unique id.
		String uniqueId = bugInstance.getUniqueId();
		if (uniqueId == null || uniqueIdToBugInstanceMap.get(uniqueId) != null) {
			assignUniqueId(bugInstance);
		}
		uniqueIdToBugInstanceMap.put(uniqueId, bugInstance);
	}

	/**
	 * Assign a unique id to given BugInstance.
	 * 
	 * @param bugInstance the BugInstance to be assigned a unique id
	 */
	private void assignUniqueId(BugInstance bugInstance) {
		String uniqueId;
		do {
			uniqueId = String.valueOf(generatedUniqueIdCount++);
		} while (uniqueIdToBugInstanceMap.get(uniqueId) != null);
		
		bugInstance.setUniqueId(uniqueId);
	}

	public boolean remove(BugInstance bugInstance) {
		boolean present = bugSet.remove(bugInstance);
		if (present) {
			uniqueIdToBugInstanceMap.remove(bugInstance.getUniqueId());
		}
		return present;
	}

	public Iterator<BugInstance> iterator() {
		return bugSet.iterator();
	}

	public Collection<BugInstance> getCollection() {
		return bugSet;
	}

	//@Override
	public void addError(String message, Throwable exception) {
		errorList.add(new AnalysisError(message, exception));
	}
	
	//@Override
	public void addError(AnalysisError error) {
		errorList.add(error);
	}
	
	public void addMissingClass(String message) {
		missingClassSet.add(message);
	}

	public Iterator<AnalysisError> errorIterator() {
		return errorList.iterator();
	}

	public Iterator<String> missingClassIterator() {
		return missingClassSet.iterator();
	}

	public boolean contains(BugInstance bugInstance) {
		return bugSet.contains(bugInstance);
	}

	public BugInstance getMatching(BugInstance bugInstance) {
		SortedSet<BugInstance> tailSet = bugSet.tailSet(bugInstance);
		if (tailSet.isEmpty())
			return null;
		BugInstance first = tailSet.first();
		return bugInstance.equals(first) ? first : null;
	}

	public String getSummaryHTML() throws IOException {
		if ( summaryHTML == null ) {
			try {
				StringWriter writer = new StringWriter();
				ProjectStats stats = getProjectStats();
				stats.transformSummaryToHTML(writer);
				summaryHTML = writer.toString();
			} catch (final TransformerException e) {
				IOException ioe = new IOException("Couldn't generate summary HTML");
				ioe.initCause(e);
				throw ioe;
			}
		}

		return summaryHTML;
	}

	public ProjectStats getProjectStats() {
		return projectStats;
	}
	
	/* (non-Javadoc)
	 * @see edu.umd.cs.findbugs.BugCollection#lookupFromUniqueId(java.lang.String)
	 */
	public BugInstance lookupFromUniqueId(String uniqueId) {
		return uniqueIdToBugInstanceMap.get(uniqueId);
	}

	// @Override
	public long getTimestamp() {
		return timestamp;
	}

	// @Override
	public void setTimestamp(long timestamp) {
		this.timestamp = timestamp;
	}
}

// vim:ts=4

@@ -4148,16 +4148,27 @@
 l) %7B%0A%09%09%09
+uniqueId = 
 assignUn
@@ -4188,16 +4188,16 @@
 tance);%0A
-
 %09%09%7D%0A%09%09un
@@ -4386,20 +4386,22 @@
 private 
-void
+String
  assignU
@@ -4580,16 +4580,16 @@
 ll);%0A%09%09%0A
-
 %09%09bugIns
@@ -4613,24 +4613,43 @@
 (uniqueId);%0A
+%09%09return uniqueId;%0A
 %09%7D%0A%0A%09public 

Make sure unique id map is actually created correctly.