/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache.compat;

import java.beans.XMLDecoder;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.jboss.tools.ClassVersionInfo;
import org.jboss.tools.SerialVersionUID;

/** 
 * Tests of serial version uid compatibility across jboss versions. The
 * testsuite/etc/serialVersionUID/xxx.ser is created using the
 * org.jboss.tools.SerialVersionUID utility.
 *
 * @author Scott.Stark@jboss.org
 */
public class SerialVersionUIDUnitTestCase extends TestCase
{
   private static final String DB_DIR = "etc/svuid-db";
   private static final String DB_FILE_PREFIX = "svuid-";
   private static final String VERSION_STRING_1_3_0_GA = "1.3.0.GA";
   private static final String VERSION_STRING_1_4_0_GA = "1.4.0.GA";
   private static final String JAVA_VERSION_PREFIX_14 = "14";
   private static final String JAVA_VERSION_PREFIX_50 = "50";

   private static String BLACKLIST_1_3_0_GA[] =
   {
      // JBCACHE-918
      "org.jboss.cache.OptimisticTreeNode", 
      "org.jboss.cache.optimistic.DefaultDataVersion",
      "org.jboss.cache.optimistic.FqnComparator",
   };

   int mismatchCount = 0;

   StringBuffer messages = null;

   public SerialVersionUIDUnitTestCase(String name)
   {
      super(name);
   }

   protected void setUp()
   {
      mismatchCount = 0;

      if (messages == null)
      {
         messages = new StringBuffer();
      }
   }

   protected void tearDown()
   {
      messages.setLength(0);
   }

   public void testCompat_1_3_0_GA() throws Exception
   {
      String prefix = getFilePrefix() + VERSION_STRING_1_3_0_GA;
      Map db = readDatabase(prefix);
      mismatchCount = compare(db, calcClassInfoMap(), prefix,
			      BLACKLIST_1_3_0_GA, messages);

      System.out.println("serialVersionUID mismatches = " + mismatchCount);
      if (mismatchCount != 0)
      {
         fail("Failures for :" + messages.toString());
      }
   }

   public void testCompat_1_4_0_GA() throws Exception
   {
      String prefix = getFilePrefix() + VERSION_STRING_1_4_0_GA;
      Map db = readDatabase(prefix);
      mismatchCount = compare(db, calcClassInfoMap(), prefix,
			      new String[0], messages);

      System.out.println("serialVersionUID mismatches = " + mismatchCount);
      if (mismatchCount != 0)
      {
         fail("Failures for :" + messages.toString());
      }
   }

   private String getFilePrefix()
   {
      String javaVersion = System.getProperty("java.version").trim();
      if (javaVersion.startsWith("1.5."))
      {
         return DB_FILE_PREFIX + JAVA_VERSION_PREFIX_50 + ".";
      }
      else if (javaVersion.startsWith("1.4."))
      {
         return DB_FILE_PREFIX + JAVA_VERSION_PREFIX_14 + ".";
      }
      else
      {
         return "";
      }
   }

   private Map readDatabase(String dbFilePrefix)
      throws Exception
   {
      File dbFile = new File(DB_DIR, dbFilePrefix + ".xml");
      FileInputStream inputStream = new FileInputStream(dbFile);
      XMLDecoder xmlObjects = new XMLDecoder(inputStream);
      ClassVersionInfo svuidInfo;
      TreeMap db = new TreeMap();
      boolean doQuit = false;

      do
      {
         try
         {
            svuidInfo = (ClassVersionInfo) xmlObjects.readObject();
            db.put(svuidInfo.getName(), svuidInfo);
         }
         /*
          * TODO: Keep working ?
         catch (ClassCastException e)
         {
            ;
         }
         */
         catch (ArrayIndexOutOfBoundsException e)
         {
            // No more data
            doQuit = true;
         }
      } while (doQuit == false);

      return db;
   }

   /**
    Compare two sets of classes for serialVersionUID compatibility.

    @param classInfoMap - the legacy version classes
    @param currentClassInfoMap - the current build classes
    @param versionName - the legacy version name
    @param badPackages - a list of package prefixes to ignore for errors
    @return the number of serial version mismatches
    */
   private int compare(Map classInfoMap, Map currentClassInfoMap,
      String versionName, String[] badPackages, StringBuffer bufferMessages)
      throws IOException
   {
      File out = new File(versionName+".errors");
      System.out.println("Writing errors to: "+out.getAbsolutePath());
      FileWriter errors = new FileWriter(out);
      int mismatchCount = 0;
      Iterator iter = currentClassInfoMap.values().iterator();
      while( iter.hasNext() )
      {
         ClassVersionInfo cvi = (ClassVersionInfo) iter.next();
         String name = cvi.getName();
         ClassVersionInfo cviLegacy = (ClassVersionInfo) classInfoMap.get(name);
         if( cviLegacy != null && cvi.getSerialVersion() != cviLegacy.getSerialVersion() )
         {
            String msg = "serialVersionUID error for "+name
               +", " + versionName + ": " + cviLegacy.getSerialVersion()
               +", current: "+cvi.getSerialVersion();

            // Don't count classes from badPackages
            boolean isInBadPkg = false;
            for(int n = 0; n < badPackages.length; n ++)
            {
               String pkg = badPackages[n];
               if( name.startsWith(pkg) )
               {
                  isInBadPkg = true;
                  break;
               }
            }

            if (isInBadPkg)
               continue;

            if (mismatchCount>0)
            {
               bufferMessages.append(",\n");
            }
            bufferMessages.append(name);
            mismatchCount ++;
            System.err.println(msg);
            errors.write(msg);
            errors.write('\n');
         }
      }
      errors.close();
      // If the mismatchCount is 0 remove the error file
      if( mismatchCount == 0 )
         out.delete();

      return mismatchCount;
   }

   static Map calcClassInfoMap()
      throws IOException
   {
      SerialVersionUID.processProperties();
      Map classInfoMap = SerialVersionUID.processJars();
      return classInfoMap;
   }
   
   public static Test suite() throws Exception
   {
      TestSuite suite = new TestSuite();
      suite.addTest(new SerialVersionUIDUnitTestCase("testCompat_1_4_0_GA"));
      suite.addTest(new SerialVersionUIDUnitTestCase("testCompat_1_3_0_GA"));

      return suite;
   }

   public static void main(String[] args)
   {
      junit.textui.TestRunner.run(SerialVersionUIDUnitTestCase.class);
   }
}
