Fixed: Issue24 OOM errors in SuiteHTMLReporter

Added Utils.appendToFile() method to avoid creating a huge stringbuffer before writing out to file.

Ran a Factory test with 2000 instances before and after the changes to ensure that OOM error doesn't occur anymore.
diff --git a/CHANGES.txt b/CHANGES.txt
index e78b5f6..e3eaaee 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -3,6 +3,7 @@
 Added: New ant task tag:  propertyset (Todd Wells)
 Added: ITestNGListenerFactory
 Added: Passing command line properties via the ant task and doc update (Todd Wells)
+Fixed: Issue24 OOM errors in SuiteHTMLReporter
 Fixed: Time outs specified in XML were not honored for <suite parallel="tests">
 Fixed: <suite> and <test> time outs were hardcoded, they now honor their time-out attribute
 Fixed: TestNG was hanging if no test methods were found
diff --git a/src/org/testng/internal/Utils.java b/src/org/testng/internal/Utils.java
index f71ec8e..5e34d9a 100755
--- a/src/org/testng/internal/Utils.java
+++ b/src/org/testng/internal/Utils.java
@@ -1,6 +1,5 @@
 package org.testng.internal;
 
-
 import org.testng.ITestNGMethod;
 import org.testng.TestNGCommandLineArgs;
 import org.testng.TestRunner;
@@ -24,7 +23,6 @@
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -142,7 +140,7 @@
   public static void writeUtf8File(String outputDir, String fileName, String sb) {
     final String outDirPath= outputDir != null ? outputDir : "";
     final File outDir= new File(outDirPath);
-    writeFile(outDir, fileName, sb, "UTF-8");    
+    writeFile(outDir, fileName, sb, "UTF-8", false);    
   }
   
   /**
@@ -156,7 +154,20 @@
   public static void writeFile(String outputDir, String fileName, String sb) {
     final String outDirPath= outputDir != null ? outputDir : "";
     final File outDir= new File(outDirPath);
-    writeFile(outDir, fileName, sb, null);
+    writeFile(outDir, fileName, sb, null, false);
+  }
+  
+  /**
+   * Appends contents of the string to the specified file. If output directory/file don't
+   * exist, they are created.
+   * @param outputDir output directory. If <tt>null</tt>, then current directory is used
+   * @param fileName file name
+   * @param sb string to be appended to file
+   */
+  public static void appendToFile(String outputDir, String fileName, String sb) {
+     final String outDirPath= outputDir != null ? outputDir : "";
+     final File outDir= new File(outDirPath);
+     writeFile(outDir, fileName, sb, null, true);
   }
   
   /**
@@ -167,7 +178,7 @@
    * @param fileName the filename
    * @param sb the file content
    */
-  private static void writeFile(File outDir, String fileName, String sb, String encoding) {
+  private static void writeFile(File outDir, String fileName, String sb, String encoding, boolean append) {
     try {
       if (!outDir.exists()) {
         outDir.mkdirs();
@@ -175,10 +186,11 @@
       
       fileName = replaceSpecialCharacters(fileName);
       File outputFile = new File(outDir, fileName);
-      outputFile.delete();
-      outputFile.createNewFile();
-      
-      writeFile(outputFile, sb, encoding);
+      if (!append) {
+        outputFile.delete();
+        outputFile.createNewFile();
+      }
+      writeFile(outputFile, sb, encoding, append);
     }
     catch (IOException e) {
       if (TestRunner.getVerbose() > 1) {
@@ -190,16 +202,16 @@
     }
   }
 
-  private static void writeFile(File outputFile, String sb, String encoding) {
+  private static void writeFile(File outputFile, String sb, String encoding, boolean append) {
     BufferedWriter fw = null;
     try {
-      if (! outputFile.exists()) outputFile.createNewFile();
+      if (!outputFile.exists()) outputFile.createNewFile();
       OutputStreamWriter osw= null;
-      if(null != encoding) {
-        osw= new OutputStreamWriter(new FileOutputStream(outputFile, false), encoding);
+      if (null != encoding) {
+        osw = new OutputStreamWriter(new FileOutputStream(outputFile, append), encoding);
       }
       else {
-        osw= new OutputStreamWriter(new FileOutputStream(outputFile, false));
+        osw = new OutputStreamWriter(new FileOutputStream(outputFile, append));
       }
       fw = new BufferedWriter(osw);
       fw.write(sb);
diff --git a/src/org/testng/reporters/SuiteHTMLReporter.java b/src/org/testng/reporters/SuiteHTMLReporter.java
index 36fe0fe..b9e2731 100755
--- a/src/org/testng/reporters/SuiteHTMLReporter.java
+++ b/src/org/testng/reporters/SuiteHTMLReporter.java
@@ -340,11 +340,12 @@
     sb.append("<h3>" + BEFORE + " means before, " + AFTER + " means after</h3><p/>");
     
     long startDate = -1;
-    Map<Long, StringBuffer> tables = Maps.newHashMap();
     sb.append("<br/><em>").append(suite.getName()).append("</em><p/>");
     sb.append("<small><i>(Hover the method name to see the test class name)</i></small><p/>\n");
+    Utils.writeFile(getOutputDirectory(xmlSuite), outputFileName, sb.toString());
+    sb = null; //not needed anymore
+
     Collection<ITestNGMethod> invokedMethods = suite.getInvokedMethods();
-    
     if (alphabetical) {
       @SuppressWarnings({"unchecked"})
       Comparator<? super ITestNGMethod>  alphabeticalComparator = new Comparator(){
@@ -357,12 +358,12 @@
       Collections.sort((List) invokedMethods, alphabeticalComparator);
     }
     
+    SimpleDateFormat format = new SimpleDateFormat("yy/MM/dd HH:mm:ss");
+    StringBuffer table = new StringBuffer();
+    boolean addedHeader = false;
     for (ITestNGMethod tm : invokedMethods) {
-      Long id = new Long(0);      
-      StringBuffer table = tables.get(id);
-      if (null == table) {
-        table = new StringBuffer();
-        tables.put(id, table);
+      table.setLength(0);
+      if (!addedHeader) {
         table.append("<table border=\"1\">\n")
           .append("<tr>")
           .append("<th>Time</th>")
@@ -376,6 +377,7 @@
           .append("<th>Thread</th>")
           .append("<th>Instances</th>")
           .append("</tr>\n");
+        addedHeader = true;
       }
       String methodName = tm.toString();
       boolean bc = tm.isBeforeClassConfiguration();
@@ -406,7 +408,6 @@
       }
       
       if (startDate == -1) startDate = tm.getDate();
-      SimpleDateFormat format = new SimpleDateFormat("yy/MM/dd HH:mm:ss");
       String date = format.format(tm.getDate());
       table.append("<tr bgcolor=\"" + createColor(tm) + "\">")
         .append("  <td>").append(date).append("</td> ")
@@ -421,19 +422,9 @@
         .append("  <td>").append(instances).append("</td> ")
         .append("</tr>\n")
         ;
+      Utils.appendToFile(getOutputDirectory(xmlSuite), outputFileName, table.toString());
     }
-    
-    /// Close all the tables
-    for (StringBuffer table : tables.values()) {
-      table.append("</table>\n");
-      sb.append(table.toString());
-    }
-    Utils.writeFile(getOutputDirectory(xmlSuite), outputFileName, sb.toString());    
-    
-  }
-  
-  private static String toHex(int n) {
-    return Integer.toHexString(0x10 | n).substring(1).toUpperCase();
+    Utils.appendToFile(getOutputDirectory(xmlSuite), outputFileName, "</table>\n");
   }
 
   /**