/*
 * Copyright (c) 2007, 2009, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

/*
 * $Id: TSTestFinder.java 70880 2013-03-20 15:48:28Z kgrucci $
 */


package com.sun.ts.lib.harness;

import com.sun.javatest.TestDescription;
import com.sun.javatest.TestFinderQueue;
import com.sun.javatest.finder.CommentStream;
import com.sun.javatest.finder.TagTestFinder;
import com.sun.ts.lib.util.AssertionMapper;
import com.sun.ts.lib.util.TestUtil;
import com.sun.ts.lib.deliverable.PropertyManagerInterface;
import com.sun.ts.lib.deliverable.DeliverableFactory;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
import java.util.Enumeration;


/**
 * This is a specific implementation of the TagTestFinder which is to be used
 * for J2EE-TS testing.
 *
 * A test description consists of a single block comment in Java files.
 * A file may contain multiple test descriptions.
 *
 * @author Vella Raman
 * @see com.sun.javatest.TestFinder
 * @see com.sun.javatest.finder.TagTestFinder
 */
public class TSTestFinder extends TagTestFinder {

// --------------------------------------------------------------- Static fields
    public static final String TS_HOME;

    protected static final File[] NON_TEST_DIRS;
    protected static final String PARSE_TAG_BAD = "Invalid tag: ";

    private static final String INTEROP_FORWARD = "forward";
    private static final String INTEROP_REVERSE = "reverse";
    private static final String INTEROP_BOTH    = "both";
    private static final String SRC_TOKEN = File.separator + "src" + File.separator;

    private static final String[] defaultVehicles = {
            "ejb", "servlet", "jsp", "appclient"
        };

    private static final String[] EXCLUDE_NAMES = {
        "SCCS",
        "Codemgr_wsdata",
        "DeletedFiles",
        "DELETED-FILES",
        "deleted_files",
        "TemporarilyRemoved",
        "ts_dep"
    };

    private static final String KEY_CLASS_SETUP_PROPS     = "class.setup_props:";
    private static final String KEY_CLASS_TEST_ARGS       = "class.testArgs:";
    private static final String KEY_TEST_NAME             = "testName:";
    private static final String KEY_TEST_ARGS             = "testArgs:";
    private static final String KEY_CUSTOM                = "custom:";
    private static final String KEY_TEST_CASE_SETUP_PROPS = "tcase.setup_props:";
    private static final String KEY_ASSERTION_IDS         = "assertion_ids:";
    private static final String KEY_KEYWORDS              = "keywords:";

    private static final String[] VALID_TAG_NAMES = {
        KEY_CLASS_SETUP_PROPS,
        KEY_CLASS_TEST_ARGS,
        KEY_TEST_NAME,
        KEY_TEST_ARGS,
        KEY_CUSTOM,
        KEY_TEST_CASE_SETUP_PROPS,
        KEY_ASSERTION_IDS,
        KEY_KEYWORDS
    };
    private static final boolean PROCESS_ASSERTION;
    private static final boolean REPORT_MISSING;
    private static final boolean ASSERTION_COVERAGE;


    static {
        TS_HOME = System.getProperty("TS_HOME");
        PROCESS_ASSERTION = Boolean.getBoolean("process.assertion");
        REPORT_MISSING = Boolean.getBoolean("report.missing.assertion.ids.tag");
        ASSERTION_COVERAGE = Boolean.getBoolean("assertion.coverage");

        NON_TEST_DIRS = new File[] {
            new File(TS_HOME, "src/web"),
            new File(TS_HOME, "src/org/omg/stub"),
            new File(TS_HOME, "src/com/sun/ts/lib"),
            new File(TS_HOME, "src/com/sun/ts/tests/common")
        };

    }


// ------------------------------------------------------------- Instance fields
    protected boolean bJCKServiceTest;
    protected String sInteropDirections = "forward";
    protected File appTestDir;


    private static TSKeywords tsKeywords;
    private VehicleVerifier vehicleVerifier;
    private boolean fastScan;
    private int iNumberOfAssertions = 0;
    private String sClass = "";
    private int executionMode = ExecutionMode.DEFAULT;
    public static Hashtable htTestNamesFound = new Hashtable();
    
    // COMMENTED OUT AS THEY ARE NEVER USED
    // private static final String LINESEP = System.getProperty("line.separator");
    // private static boolean cachedKeyList;
    // private File currFile;
    // private Hashtable excludeTable = new Hashtable();
    // private Hashtable keyList = new Hashtable(3);
    // private boolean testprop;
    // private Hashtable propTags;

// ---------------------------------------------------------------- Constructors

    /**
     * Constructs the list of file names to exclude for pruning in the search
     * for files to examine for test descriptions.  This constructor also sets
     * the allowable comment formats.
     */
    public TSTestFinder() {
        super();
        Arrays.sort(VALID_TAG_NAMES);
        exclude(EXCLUDE_NAMES);
        setInitialTag(null);

        try {
            PropertyManagerInterface propMgr = DeliverableFactory.getDeliverableInstance().getPropertyManager();
            executionMode = ExecutionMode.getExecutionMode(propMgr); 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

// -------------------------------------------------------------- Public Methods

    /**
     * Scan a file, looking for test descriptions and/or more files to scan.
     * Overwrite to filter out directories that do not contain any tests.
     * @param file The file to scan
     */
    public void scan(File file) {
        for (int i = 0; i < NON_TEST_DIRS.length; i++) {
            if (file.compareTo(NON_TEST_DIRS[i]) == 0) {
                return;
            }
        }
        super.scan(file);
    }

    public static String getAbsolutePath(String testRootPath, String path) {
        return new File(testRootPath, path).getAbsolutePath();
    }

    public static String getAbsolutePath(String path) {
        return getAbsolutePath(TS_HOME + "/src", path);
    }

    public static void main(String[] argv) {
        String sRoot = null;
        Vector vTestDescriptions = new Vector();
        TestDescription td = null;
        for (int ii = 0; ii < argv.length; ii++) {
            if (argv[ii].startsWith("-rootdir")) {
                sRoot = argv[++ii];
            } else {
                TestUtil.logHarness("Must supply a rootdir");
                System.exit(-1);
            }
        }
        TestFinderQueue tfq = new TestFinderQueue();
        TSTestFinder tf = new TSTestFinder();
        try {
            tf.init(TestUtil.EMPTY_STRING_ARRAY, new File(sRoot), null);
            tfq.setTestFinder(tf);
            tfq.setTests(new String[]{
                sRoot
            });
        } catch (Exception e) {
            TestUtil.logHarness("Exception initializing the TestFinder");
            e.printStackTrace();
        }
        while ((td = tfq.next()) != null) {
            vTestDescriptions.addElement(td);
        }
        //write all test description info to a file
        try {
            tf.writeToFile(vTestDescriptions);
            TestUtil.logHarness("Total # of Assertions = " + tf.iNumberOfAssertions);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


// ----------------------------------------------------------- Protected Methods

    /**
     * Make sure that the provide name-value pair is of the proper format as
     * described in the tag-spec.
     *
     * @param tagValues   The dictionary of the entries being read
     * @param name	The name of the entry that has been read
     * @param value	The value of the entry that has been read
     */
    protected void processEntry(Map tagValues, String name, String value) {
        if (Arrays.binarySearch(VALID_TAG_NAMES, name) < 0) {
            name = "error";
            value = PARSE_TAG_BAD + name;
        }
        super.processEntry(tagValues, name, value);
    }

    /**
     *  Override the scan method in TagTestFinder since we have to
     * deal with CommentStreams that are valid test tags ( but not
     * a test description object of themselves -- the class.setup_props
     * tag ).
     * @param file The file to scan
     */
    protected void scanFile(File file) {
        String name = file.getName();
        
        //Return immediately if we don't have a java source file
        //or we have a java file that we know we don't want to scan
        if (!name.endsWith(".java") ||
             name.equals("BaseUIComponentClient.java") ||
             name.equals("DataModelURLClient.java"))
        {
            return;
        }

        int dot = name.indexOf('.');
        if (dot == 0)
            return;
        if (TestUtil.harnessDebug)
            TestUtil.logHarnessDebug("Scanning " + name);
        String sVal = null;
        String extn = name.substring(dot);
        Class csc = getClassForExtension(extn);
        if (csc == null) {
            String[] msgs = {
                "no parser found for specified file", "file: " + file, "extension: "
                + extn
            };
            TestUtil.logErr(msgs[0] + msgs[1] + msgs[2]);
            return;
        }
        CommentStream cs = null;
        try {
            cs = (CommentStream) (csc.newInstance());
        } catch (InstantiationException e) {
            String[] msgs = {
                "problem instantiating class for extension", "extension: " + extn,
                "class: " + csc.getName()
            };
            TestUtil.logErr(msgs[0] + msgs[1] + msgs[2]);
            return;
        } catch (IllegalAccessException e) {
            String[] msgs = {
                "illegal access while instantiating class for extension", "extension: "
                + extn, "class: " + csc.getName()
            };
            TestUtil.logErr(msgs[0] + msgs[1] + msgs[2]);
            return;
        }
        try {
            cs.init(new BufferedReader(new FileReader(file)));
            if (fastScan)
                cs.setFastScan(true);

            sInteropDirections = InteropTestManager.getInteropDirections(file.getPath());
            Map propTags = new Hashtable();
            String comment;

            while ((comment = cs.readComment()) != null) {
                Map myTagValues = parseComment(comment, file);
                if (myTagValues.isEmpty()) {
                    continue;
                }
                if (myTagValues.containsKey(KEY_CLASS_SETUP_PROPS) || myTagValues.containsKey(KEY_CLASS_TEST_ARGS)) {
                    propTags = myTagValues;
                } else if (myTagValues.containsKey(KEY_TEST_NAME)) {
                    //add the directory that this file is located in, so
                    //we can use it later to deploy the jar
                    myTagValues.put("testdirectory", file.getPath());
                    if (!propTags.isEmpty()) {
                        sVal = (String) propTags.get(KEY_CLASS_SETUP_PROPS);
                        if (sVal != null) {
                            myTagValues.put(KEY_CLASS_SETUP_PROPS, sVal);
                        }
                        sVal = (String) propTags.get(KEY_CLASS_TEST_ARGS);
                        if (sVal != null) {
                            myTagValues.put(KEY_CLASS_TEST_ARGS, sVal);
                        }
                    }
                    foundTestDescription(myTagValues, file, 0);
                }
            }
        } catch (FileNotFoundException e) {
            String msgs = "can't find file " + file.getPath();
            e.printStackTrace();
            TestUtil.logErr(msgs);
        } catch (IOException e) {
            String msgs = "problem reading file " + file.getPath();
            e.printStackTrace();
            TestUtil.logErr(msgs);
            error(null, msgs);
        } catch (Throwable e) {
            String msgs = "got exception: problem reading file " + file.getPath();
            e.printStackTrace();
            TestUtil.logErr(msgs);
        } finally {
            try {
                if (cs != null)
                    cs.close();
            } catch (IOException e) {
                TestUtil.logErr(e.getMessage());
            }
        }
    }

    protected void foundTestDescription(Map entries, File file, int line) {
        String[] sVal = null;
        TestDescription td = null;
        if (!entries.isEmpty()) {            
            vehicleVerifier = VehicleVerifier.getInstance(file, (String)entries.get(KEY_TEST_NAME));
            sVal = vehicleVerifier.getVehicleSet();
            if (sVal.length > 0) {
                for (int ii = 0; ii < sVal.length; ii++) {
                    if (sInteropDirections.equals(INTEROP_FORWARD) || sInteropDirections.equals(INTEROP_BOTH)) {
                        //create 1 desc for each vehicle to be tested
                        td = createTestDescription(entries, file, sVal[ii].trim(),
                            false);
                        foundTestDescription(td);
                    }
                    if (sInteropDirections.equals(INTEROP_REVERSE) || sInteropDirections.equals(INTEROP_BOTH)) {
                        //create 1 desc for each vehicle to be tested
                        td = createTestDescription(entries, file, sVal[ii].trim(),
                            true);
                        foundTestDescription(td);
                    }
                }
            } else {
                if (sInteropDirections.equals(INTEROP_FORWARD) || sInteropDirections.equals(INTEROP_BOTH)) {
                    td = createTestDescription(entries, file, null, false);
                    foundTestDescription(td);
                }
                if (sInteropDirections.equals(INTEROP_REVERSE) || sInteropDirections.equals(INTEROP_BOTH)) {
                    td = createTestDescription(entries, file, null, true);
                    foundTestDescription(td);
                }
            }
        }
    }

// ------------------------------------------------------------- Private Methods

    private void writeToFile(Vector v) throws IOException {
        TestDescription td = null;
        FileWriter fw = new FileWriter("test_report.txt");
        int iSize = v.size();
        for (int ii = 0; ii < iSize; ii++) {
            td = (TestDescription) v.elementAt(ii);
            writeTest(td, fw);
        }
        fw.close();
    }

    private void writeTest(TestDescription td, FileWriter fw) throws IOException {
        String sName = td.getParameter("testName");
        String sAssertion = td.getParameter("assertion_ids");
        String sLastClass = sClass;
        sClass = td.getParameter("classname");
        sClass = sClass.substring(0, sClass.lastIndexOf('.'));
        if (!sClass.equals(sLastClass)) {
            fw.write(sClass + "\tTests" + TestUtil.NEW_LINE + TestUtil.NEW_LINE);
        }
        fw.write("Test:\t" + sName + TestUtil.NEW_LINE);
        fw.write("Assertion:\t" + sAssertion + TestUtil.NEW_LINE);
        fw.write(TestUtil.NEW_LINE);
        iNumberOfAssertions++;
    }

    private String[] getDefaultVehicles() {
        return defaultVehicles;
    }

    private TestDescription createTestDescription(Map tags, File file, String sVehicle,
                                                  boolean bReverse) {
        Vector propv = new Vector();
        String sval = null;
        String fn = file.getName();
        String key = null;
        String cls = fn.substring(0, fn.indexOf('.'));
        String fullclass = getPkgName(file) + '.' + cls;
        Hashtable desc = new Hashtable();
        desc.put("classname", fullclass);
        String test_dir = null;
        if (bJCKServiceTest) {
            //to id this description as a jckservicetest one
            desc.put("finder", "jck");
            test_dir = appTestDir.getAbsolutePath();
        } else {
            //to id this description as a cts one
            desc.put("finder", "cts");
            test_dir = file.getParent();
        }
        desc.put("test_directory", TestUtil.getRelativePath(test_dir));
        for (Iterator e = tags.keySet().iterator(); e.hasNext();) {
            key = ((String) e.next());

            if ((key.equals(KEY_TEST_NAME))) {
                sval = (String) tags.get(key);
                if (sval != null) sval = sval.trim();
                
                if(htTestNamesFound.containsKey((String)(file.getPath() + "#" + sval)))
                {
                    //TestUtil.logHarness("Found duplicate @testName declaration:  " +  sval);
                    //TestUtil.logHarness("Located in source file:  " + file.getName());
                    Integer iCount = (Integer)(htTestNamesFound.get((String)(file.getPath() + "#" + sval)));         
                    htTestNamesFound.put((String)(file.getPath() + "#" + sval), new Integer((iCount.intValue())+1));
                }
                else
                {
                    htTestNamesFound.put((String)(file.getPath() + "#" + sval), new Integer(1));
                } 
                
                if (sVehicle != null) {
                    sval += "_from_" + sVehicle;
                    desc.put("service_eetest", "yes");
                } else {
                    desc.put("service_eetest", "no");
                }
                if (bReverse) {
                    sval += '_' + "reverse";
                    desc.put("direction", INTEROP_REVERSE);
                } else {
                    desc.put("direction", INTEROP_FORWARD);
                }
                desc.put("id", sval);
            }
            if ((key.equals(KEY_TEST_NAME)) || (key.equals(KEY_CUSTOM))) {
                sval = (String) tags.get(key);
                if (sval != null) sval = sval.trim();
                key = key.substring(0, key.indexOf(':'));
                desc.put(key, sval);
            } else if (key.equals(KEY_ASSERTION_IDS) && PROCESS_ASSERTION) {
                sval = (String) tags.get(key);
                if (sval != null) sval = sval.trim();
                key = key.substring(0, key.indexOf(':'));
                if (ASSERTION_COVERAGE) {
                    //use the original value as specified in source code for coverage tool
                    desc.put(key, sval);
                } else {
                    desc.put(key, AssertionMapper.getAssertionDescriptions(sval,
                        file));
                }
            } else if ((key.equals(KEY_CLASS_TEST_ARGS)) || (key.equals(KEY_TEST_ARGS))) {
                String sCurrentArgs = (String) desc.get("testArgs");
                sval = (String) tags.get(key);
                if (sval == null)
                    continue;
                sval = sval.trim();
                key = key.substring(0, key.indexOf(':'));
                if (sCurrentArgs == null)
                    desc.put("testArgs", sval);
                else
                    desc.put("testArgs", sCurrentArgs + " " + sval);
            } else if ((key.equals(KEY_KEYWORDS))) {
                sval = (String) tags.get(key);
                if (sval != null)
                {
                    sval = sval.trim();
                }
                key = key.substring(0, key.indexOf(':'));

                desc.put(key, sval);
            }
            if ((key.equals(KEY_CLASS_SETUP_PROPS)) || (key.equals(KEY_TEST_CASE_SETUP_PROPS))) {
                sval = (String) tags.get(key);
                TestUtil.logHarnessDebug("KEY_TEST_CASE_SETUP_PROPS sval \""
					 + sval + "\"");
                if (sval == null)
                    continue;
                sval = sval.trim();
                // if there is no final semi-colon add one to avoid the hang
		if (!sval.endsWith(";")) {
 		    sval = sval + ";";
 		}
                TestUtil.logHarnessDebug("KEY_TEST_CASE_SETUP_PROPS normalized sval \""
					 + sval + "\"");
                int sl = sval.length() - 1;
                int start = 0;
                int count = 1;
                int sep = 0;
                int sep2 = 0;
                while (count < sl) {
                    sep = sval.indexOf(';');
                    if (sep != -1) {
                        String propdesc = sval.substring(start, sep);
                        sval = sval.substring(sep + 1);
                        //remove description (if there is a ',' in our string)
                        //we only want property names
                        if ((sep2 = propdesc.indexOf(',')) != -1)
                            propdesc = propdesc.substring(0, sep2);
                        propv.addElement(propdesc);
                        count += sep + 1;
                    } else {
			continue;
		    }
                }
            }

            // Put properties as space separated String..
            String s1 = "";
            for (int j = 0, size = propv.size(); j < size; j++)
                s1 = s1 + ' ' + propv.elementAt(j);
            desc.put("testProps", s1);
        }
        
        //add in inherent keywords here
        String sKeywords = (String)desc.get("keywords");

        if (sKeywords != null)
        {
            sKeywords = sval.trim() + " " + sKeywords;
            sKeywords += " ";
        }
        else
        {
            sKeywords = "";
        }
        
        //default keyword for all tests not containing keywords in the src tags
        sKeywords += "all ";
            
        //Get keywords from keyword.properties file
        tsKeywords = TSKeywords.getInstance(file);
        String[] sKeywordsFromProperties = tsKeywords.getKeywordSet();
        
        for(int ii = 0; ii < sKeywordsFromProperties.length; ii++)
        {

            //For any technologies that may be run on a web profile impl,
            //only attach the javaee_web_profile or <techname>_web_profile keywords if
            //the vehicle in use for the test is required by a web profile impl.
            if(sKeywordsFromProperties[ii].contains("web_profile") &&
               sVehicle != null)
            {
                if((sVehicle.contains("web") ||
                   sVehicle.contains("servlet") ||
                   sVehicle.contains("jsp") ||
                   sVehicle.contains("ejbembed") ||
                   sVehicle.contains("ejblite") ||
                   sVehicle.contains("jsf")) && !sVehicle.contains("puservlet"))
                {
                    sKeywords += sKeywordsFromProperties[ii] + " ";
                }
            }
            else
            {
                sKeywords += sKeywordsFromProperties[ii] + " ";
            }
        }
            
        //Add test name as a keyword.  This allows you to specify a test name as
        //a keyword to subset tests.  Useful if you have a bunch of leaf
        //directories containing the same test names and you want to run just
        //those tests from each dir in a single run.
        sKeywords += desc.get("testName") + " ";
        
        //Add the vehicle as a standlone keyword.  This is useful
        //if you want to use expressions when specifying keywords like the 
        //following... javaee.webprofile | connector & web.vehicle
        if (sVehicle != null) 
        {
            sKeywords += sVehicle + "_vehicle ";
        }
        else
        {
            sKeywords += "novehicle ";
        }
        
        //add keywords for forward and reverse tests
        if (bReverse) {
            sKeywords += "reverse";
        } else {
            sKeywords += "forward";
        }
        desc.put("keywords", sKeywords);

        //Print out the keywords for this test description
        TestUtil.logHarnessDebug("Keywords for this test, " + 
                (String) tags.get(KEY_TEST_NAME) + ", are:  " + sKeywords);
        
        if (REPORT_MISSING && !desc.containsKey("assertion_ids")) {
            AssertionMapper.log("WARN: missing assertion_ids tag in testName " +
                desc.get("testName") + " in file " + file.getPath());
        }
        File root = this.getRoot();
        return new TestDescription(root, file, desc);
    }

    private String getPkgName(File clientFile) {
        String result = null;
        String clientFileDir = clientFile.getParent();
        int i = clientFileDir.indexOf(TSTestFinder.SRC_TOKEN);
        if (i != -1) {
            result = clientFileDir.substring(i + 5);
            result = result.replace(File.separatorChar, '.');
        } else {
            result = getPkgName2(clientFile);
        }

        return result;
    }

    private String getPkgName2(File tfile) {
        String pkgName = null;
        String line = null;
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(tfile));
            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (line.length() > 0 && line.startsWith("package") && line.endsWith(";")) {
                    int pkgstart = line.indexOf(' ');
                    pkgName = line.substring(pkgstart, line.indexOf(';'));
                    return pkgName.trim();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (Exception except) {
                }
            }
        }
        return pkgName;
    }
}
