package test.mockobjects;
import java.io.File;
import java.lang.reflect.Modifier;
import java.util.StringTokenizer;
import junit.framework.*;


/** A TestSuite containing all test classes found on the class path in a
 *  hierarchy that matches the class-path and package structure of the system.
 */
public class AutoTestSuite
    extends TestSuite
{
    /** Constructs a SystemTestSuite by finding all the test classes and
     *  building the test hierarchy.
     */
    public AutoTestSuite() {
        super("System Tests");
        
        String class_path= System.getProperty("java.class.path");
        String separator= System.getProperty("path.separator");
        StringTokenizer path_elements = 
            new StringTokenizer( class_path, separator );
        
        while( path_elements.hasMoreTokens() ) {
            File root = new File( path_elements.nextToken() );
            
            if( root.isDirectory() ) {
                TestSuite sub_suite =
                    new AutoTestSuite( root.toString(), root, root );
                
                if( sub_suite.testCount() > 0 ) addTest( sub_suite );
            }
        }
    }
    
    private AutoTestSuite( String name, File root, File dir ) {
        super( name );
        
        File[] contents = dir.listFiles();
        
        for( int i = 0; i < contents.length; i++ ) {
            File f = contents[i];
            
            if( f.isDirectory() ) {
                addNonEmptySuite(new AutoTestSuite( f.getName(), root, f ));
                
            } else if( isTestClass(f) ) {
                try {
                    Class test_class = fileToClass( root, f );
                    if( isInstantiable(test_class) ) {
                        addNonEmptySuite(new TestSuite(test_class));
                    }
                }
                catch( ClassNotFoundException ex ) {
                    System.err.println("failed to load class " + f );
                    ex.printStackTrace();
                    // Continue adding other classes to the test suite
                }
            }
        }
    }
    
    private void addNonEmptySuite(TestSuite suite) {
        if( suite.testCount() > 0 ) {
            addTest( suite );
        }
    }
    
    private boolean isInstantiable( Class c ) {
        int mods = c.getModifiers();
        
        return !Modifier.isAbstract(mods) &&
               !Modifier.isInterface(mods) &&
               Modifier.isPublic(mods);
    }
    
    /** Is `f' a class-file containing a test case?
     */
    protected boolean isTestClass( File f ) {
        String name = f.getName();
        return name.endsWith("Test.class") ||
               ( name.endsWith(".class") &&
                 name.startsWith("Test") &&
                 !isFilenameOfInnerClass(name) );
    }
    
    private boolean isFilenameOfInnerClass(String name) {
        return name.indexOf('$') >= 0;
    }
    
    private Class fileToClass( File root, File f ) 
        throws ClassNotFoundException 
    {
        String class_name = pathToClassName( root.toString(), f.toString() );
        return Class.forName( class_name );
    }
    
    private String pathToClassName( String root, String f ) {
        int root_len = root.length();
        if( !root.endsWith("/") ) root_len++;
        int tail_len = f.length() - ".class".length();
        
        return f.substring( root_len, tail_len ).replace('/','.');
    }
    
    /** Constructs and returns a SystemTestSuite.
     */
    public static TestSuite suite() {
        return new AutoTestSuite();
    }
    
    public static void main( String[] args ) {
        junit.swingui.TestRunner.main( new String[]{ 
            AutoTestSuite.class.getName() 
        } );
    }
}
