/********************************************************************
 * Copyright © 2007 VHA. All rights reserved
 ********************************************************************/


package gov.va.med.fw.util;

import gov.va.med.fw.service.AbstractComponent;

import java.io.File;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 *  Utility to check Serial Version UID in Serializable file. 
 * @author Venky Kullampalle
 */
public class CheckSerialVersionUtil extends AbstractComponent {
	

    private static final int MASK = Modifier.STATIC | Modifier.FINAL;
    
    public static final String MISSING_SER_LIST="missingSerList";
    
    public static final String ERROR_LIST="errorList";
    
    /**
     * Checks class file directory hierarchies for serializable classes
     * that do not have explicit <code>serialVersionUID</code> fields,
     * and adds the names of such classes to the list. Each such class
     * is loaded from the class loader of this tool. If the class is not an
     * interface, directly or indirectly implements {@link Serializable},
     * and does not have a declared <code>static</code> <code>final</code>
     * field named <code>serialVersionUID</code>, then the name of the class
     * is added to the list.
     * If for some reason class loader fails to load a class, name of such class
     * is added to the error list     
     * @param clsRootDirectory
     * @return
     * @throws Exception
     */
    public static Map checkSerialVersionUID(String clsRootDirectory) throws Exception {
           
        if (StringUtils.isEmpty(clsRootDirectory))
            throw new Exception("Classes root directory is null");
            // missingSerList --> List of file Name
            // errorList-- > List of failed to load class file names.
            Map retMap=new HashMap();
            
            List missingSerList=new ArrayList();
            List errorList=new ArrayList(); 
            
            check(clsRootDirectory, clsRootDirectory.length() + 1, missingSerList, errorList);
            
            if(!missingSerList.isEmpty())
                retMap.put(MISSING_SER_LIST, missingSerList);
            if(!errorList.isEmpty())
            {
                retMap.put(ERROR_LIST, errorList);
            }
            
            return retMap;
    }

    /**
     * Checks the class file directory hierarchy starting from the specified
     * directory. In the hierarchy, each file with a name ending in the
     * suffix <code>.class</code> is treated as a class file; the
     * corresponding class name is obtained from the filename by stripping
     * off the first <code>strip</code> characters of prefix and the
     * <code>.class</code> suffix, and replacing each file separator
     * character with a period (<code>.</code>). Each such class is loaded
     * from the class loader of this tool. If the class is not an interface,
     * directly or indirectly implements {@link Serializable}, and does not
     * have a declared <code>static</code> <code>final</code> field named
     * <code>serialVersionUID</code>, then the name of the class is printed
     * to the standard output stream.
     *
     * @param dir directory hierarchy root
     * @param strip number of characters of prefix to strip from each
     * class file name
     */
    public static void check(String clsRootDirectory, int strip, List missingSerList, List errorList ) {
        
        String[] files = new File(clsRootDirectory).list();     
        // If directory is empty return
        if (files == null) {
            return;
        }
        
        // Loops through the list of files, calls checkClass method if it is class file
        //otherwise call check Recursively
        for (int i = 0; i < files.length; i++) 
        {
            String file = clsRootDirectory + File.separatorChar + files[i];
            if (file.endsWith(".class"))                
                 checkClass(file, strip,missingSerList, errorList);         
            else if (new File(file).isDirectory())
                check(file, strip, missingSerList, errorList);
        }
    }

   /**
    * 
    * @param file
    * @param strip
    * @return
    * @throws ClassNotFoundException
    * @throws NoClassDefFoundError
    */
    private static void checkClass(String file, int strip, List missingSerList, List errorList)  {
        
        file = file.substring(strip, file.length() - 6);
        file = file.replace(File.separatorChar, '.');
        try {
            Class c = Class.forName(file);
            if (c.isInterface() || !Serializable.class.isAssignableFrom(c))
            return ;
            Field f = c.getDeclaredField("serialVersionUID");
            if ((f.getModifiers() & MASK) != MASK)
                missingSerList.add(file);  
        } catch (ClassNotFoundException e) {
            errorList.add(file);
        } catch (NoClassDefFoundError e) {
            errorList.add(file);           
        } catch (NoSuchFieldException e) {
                missingSerList.add(file);
        }
    }
    
}
