/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/*
 * VerifierTest.java
 *
 * Created on September 20, 2000, 4:18 PM
 */

package com.sun.enterprise.tools.verifier.tests;

import java.lang.reflect.Method;
import java.io.File;
import java.util.jar.JarFile;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import java.util.logging.Level;

import com.sun.enterprise.tools.verifier.Verifier;
import com.sun.enterprise.tools.verifier.StringManagerHelper;
import com.sun.enterprise.tools.verifier.Result;
import com.sun.enterprise.tools.verifier.Context;
import com.sun.enterprise.deployment.deploy.shared.FileArchive;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.DocumentType;
import com.sun.org.apache.xpath.internal.XPathAPI;
import com.sun.org.apache.xpath.internal.NodeSet;
import com.sun.org.apache.xml.internal.utils.PrefixResolver;
import com.sun.enterprise.tools.verifier.XpathPrefixResolver;
import com.sun.enterprise.logging.LogDomains;
import com.sun.org.apache.xpath.internal.objects.XObject;

/**
 * Superclass for all tests developped for the Verifier Harness
 * Contains convenience methods and fields
 *
 * @author  Jerome Dochez
 * @version 
 */
abstract public class VerifierTest extends Object {
    // variables ensuring that result details are added only once
    private boolean addedError   = false;
    private boolean addedGood    = false;
    private boolean addedNa      = false;
    private boolean addedWarning = false;
    protected Logger logger = LogDomains.getLogger(LogDomains.AVK_VERIFIER_LOGGER);


   /**
     * <p>
     * Are we in debug mode 
     * </p>
     */
    protected final boolean debug = Verifier.isDebug();

    /**
     * <p>
     * helper property to get to the localized strings
     * </p>
     */
    protected static final com.sun.enterprise.util.LocalStringManagerImpl smh =
	StringManagerHelper.getLocalStringsManager();

    private Context context = null;

    /**
     * This method sets the Context object
     */
    public void setVerifierContext(Context context) {
	this.context = context;
    }
    
    /**
     * This method provides the Context object
     */
    public Context getVerifierContext() {
	return context;
    }
    

    /**
     * <p>
     * Common traces and initialization of the result object that will
     * hold the result of the assertion tested by this verifier test
     * </p>
     * 
     * @return the initialized Result object
     */     
    protected Result getInitializedResult() {

        logger.log(Level.FINE, "which.class.called.string",
                new Object[] {getClass()}) ;
        Result result = new Result();
        String version = "";
        // This is only needed because of ParseDD test which runs before
        // context is set in VerifierTest object, else we shall get a NPE.
        String compName = "";
        if (context!=null) {
            version = context.getSchemaVersion();
            compName = context.getComponentNameConstructor().toString();
        } else {
            logger.fine(getClass().getName() + " context is null.");
        }
        result.init(getClass(), version, compName);
        logger.log(Level.FINE, "test.string.assertion", new Object[] {result.getAssertion()});
        return result;
    }

    /**
     * <p>
     * check if the class is a sublcass or the class itself of the passed class name
     * </p>
     * 
     * @param subClass class object to test if it a subclass 
     * @param superClassName class name for the superclass
     * @return true if the <code>Class</code> is a subclass or the class itself
     * of the class name 
     */
    public static boolean isSubclassOf(Class subClass, String superClassName) {
        
        
        if (subClass==null || superClassName==null) {
            return false;
        }
        
        Class c = subClass;
        
	do {
            if (c.getName().equals(superClassName)) {
                return true;
            }
	    Class[] interfaces = c.getInterfaces();
            for (int i=0; i<interfaces.length;i++) {
                if (interfaces[i].getName().equals(superClassName)) {
		    return true;
                }
		else {
		    if (isSubclassOf(interfaces[i], superClassName)) {
			return true;
		    }
		}
            }
            c = c.getSuperclass(); 	             
	} while (c!=null);
        return false;
    }
    
    /**
     * <p>
     * Test if a class or its superclasses implements an interface
     * </p>
     * 
     * @param c is the class to test 
     * @param interfaceName is the interface we test for implementation
     * @return true if the class or superclasses implements the interface
     */
    public static boolean isImplementorOf(Class<?> c, String interfaceName) {
        
        if (c==null || interfaceName==null) 
            return false;

        // try this first because the code in the rest of the method
        // is buggy 
        try {
          Class<?> intf = Class.forName(interfaceName);
          if (intf.isAssignableFrom(c)) 
             return true;
          else
             return false;
         }catch(Exception e) { }
        
	do {
	    if (isSubclassOf(c, interfaceName)) {
		return true;
	    }
	    // get the list of implemented interfaces
            Class[] interfaces = c.getInterfaces();
            for (int i=0; i<interfaces.length;i++) {
		if (isSubclassOf(interfaces[i], interfaceName)) {
		    return true;
                }
            }
            // we haven't found for this implementation, look in the superclass
            c = c.getSuperclass();
        } while (c != null);
	return false;        
    }    

    /**
     * <p>
     * utility method to return a method if it is implemented by a class or one
     * of its superclass irrespective of the method being public, private or protected
     * </p>
     * 
     * @param clazz the class used to look up the method
     * @param methodName the method name
     * @param parmTypes the parameters 
     * @return instanceof the <code>Method</code> if implemented
     */
    public static Method getDeclaredMethod(Class clazz, String methodName, Class[] parmTypes)
    {
        Method m=null;
        Class c = clazz;
        do {
            try {
                m = clazz.getDeclaredMethod(methodName, parmTypes);
            } catch(NoSuchMethodException nsme) {
            } catch(SecurityException se) {
            }
            c = c.getSuperclass();
        } while (m != null && c != null);            
        return m;
    }
    
    /**
     * <p>
     * utility method to return a method if ig is implemented by a class or one
     * of its superclass and it is defined as public
     * </p>
     * 
     * @param clazz the class used to look up the method
     * @param methodName the method name
     * @param parmTypes the parameters 
     * @return instanceof the <code>Method</code> if implemented
     */
    public static Method getMethod(Class clazz, String methodName, Class[] parmTypes)
    {
        Method m=null;
        Class c = clazz;
        do {
            try {
                m = clazz.getMethod(methodName, parmTypes);
            } catch(NoSuchMethodException nsme) {
            } catch(SecurityException se) {
            }
            c = c.getSuperclass();
        } while (m != null && c != null);            
        return m;
    }
    
    /**
     * <p>
     * verify that a class or one of its superclass is implementing an interface
     * </p>
     *
     * @param clazz the class to test for the implementation of the interface
     * @param interfaceName the name of the interface that should be implementad
     * @param result where to put the result
     * 
     */
    public static boolean testImplementationOf(Class clazz, String interfaceName, Result result) 
    {        
        if (isImplementorOf(clazz, interfaceName)) {
            result.passed(smh.getLocalString
	        ("com.sun.enterprise.tools.verifier.tests.VerifierTest.interfaceimplementation.passed", 
                "The class [ {0} ] implements the [ {1} ] interface",
                new Object[] {clazz.getName(), interfaceName}));    
            return true;
        } else {
            result.failed(smh.getLocalString
	        ("com.sun.enterprise.tools.verifier.tests.VerifierTest.interfaceimplementation.failed", 
                "Error: The class [ {0} ] does not implement the [ {1} ] interface",
                new Object[] {clazz.getName(), interfaceName}));        
            return false;
        }
    }    
    
    /** 
     * <p>
     * Test for a file existence in the archive file
     * </p>
     *
     * @param uri The archive to look in
     * @param fileName The file Name to look for
     * @param fileID The archive file name
     * @param result where to place the result
     */
    public static void testFileExistence(String uri, String fileName, String fileID, Result result) {
        
       FileArchive arch=null;
//       ZipEntry ze=null;
       JarFile jarFile=null;
       
       if (fileName == null || fileName.length()==0) {
	    result.notApplicable(smh.getLocalString
    	        ("com.sun.enterprise.tools.verifier.tests.VerifierTest.fileexistence.notApplicable",
                 "No {0} defined in deployment descriptors",
                 new Object[] {fileID}));                        
            return;
        }
//        if (file==null){
            try{
                arch = new FileArchive();
                arch.open(uri);
            }catch(Exception e){}   
//        }else{
//            try {
//                jarFile = new JarFile(file);
//            } catch (java.io.IOException ioe) {
//                Verifier.debug(ioe);
//                result.failed(smh.getLocalString
//                        ("com.sun.enterprise.tools.verifier.tests.VerifierTest.fileexistence.failed",
//                         "Error:  {0} [ {1} ] not found in the archive",
//                         new Object[] {fileID, fileName}));      
//                         return;
//             }
//         }
        try{
//            if (file!=null){
//            ze = jarFile.getEntry(fileName);
//            if (ze == null) {
//                result.failed(smh.getLocalString
//                    ("com.sun.enterprise.tools.verifier.tests.VerifierTest.fileexistence.failed",
//                    "Error: {0} [ {1} ] not found in the archive",
//                    new Object[] {fileID, fileName}));                           
//            }else{
//                result.passed(smh.getLocalString
//                ("com.sun.enterprise.tools.verifier.tests.VerifierTest.fileexistence.passed",
//                "{0} [ {1} ] found in the archive",
//                new Object[] {fileID, fileName}));                           
//            }
//        }
//        else{
            File urif = new File(arch.getArchiveUri()+File.separator+fileName);
            if(urif.exists()){
                result.passed(smh.getLocalString
                ("com.sun.enterprise.tools.verifier.tests.VerifierTest.fileexistence.passed",
                "{0} [ {1} ] found in the archive",
                new Object[] {fileID, fileName}));                           
            }else{
                result.warning(smh.getLocalString
                    ("com.sun.enterprise.tools.verifier.tests.VerifierTest.fileexistence.warning",
                    "{0} [ {1} ] not found in the archive",
                    new Object[] {fileID, fileName}));                           
            }
                urif = null;
//        }
        
        if (jarFile!=null)
                        jarFile.close();
        }catch(Exception ex){}
    
    }
    
   /**
     * <p>
     * test if a method throws a particular exception
     * </p>
     * 
     * @param method the method to test
     * @param exception the exception we are looking for
     * @return true if the method actually throw the exception
     */
    public static boolean methodThrowException(Method method, String exception) {
        
        Class[] exceptions = method.getExceptionTypes();
        for (int i=0;i<exceptions.length;i++) {
            if (isSubclassOf(exceptions[i], exception))
                return true;            
        }
        return false;
    }

    /*
     *getXPathValueForNonRuntime(String xpath)
     *   return String - is the value of the element specified in the xpath.
     */
    public String getXPathValueForNonRuntime(String xpath){
        try{
            String value = null;
            Document d = getVerifierContext().getDocument();
            if (d==null) return null;
            XObject result = XPathAPI.eval(d, xpath, 
                             (PrefixResolver)new XpathPrefixResolver (d));
            NodeList nl = result.nodelist();
            for(int i=0; i<nl.getLength();i++){
                Node n = ((Node)nl.item(i)).getFirstChild();
                if (n==null) return null;
                value = n.getNodeValue();
            }
            return value;
        }catch(Exception ex){
            ex.printStackTrace();
            return null;
        }
    }

    /*
     *getXPathValue(String xpath)
     *   return String - is the value of the element specified in the xpath.
     */
    public String getXPathValue(String xpath){
        try{
            String value = null;
            Document d = getVerifierContext().getRuntimeDocument();
            if (d==null) return null;
            NodeList nl = XPathAPI.selectNodeList(d, xpath);
            for(int i=0; i<nl.getLength();i++){
                Node n = ((Node)nl.item(i)).getFirstChild();
                if (n==null) return null;
                value = n.getNodeValue();
            }
            return value;
        }catch(Exception ex){
            ex.printStackTrace();
            return null;
        }
    }

    /*
     * getCountNodeSet( String xpath)
     *   return the number of nodes for the specified xpath
     */
    public int getCountNodeSet (String xpath){
        try{
//            int value = -1;
            Document d = getVerifierContext().getRuntimeDocument();
            if (d==null)
                return -1;
            NodeSet ns = new NodeSet(XPathAPI.selectNodeList(d, xpath));
            return ns.getLength();
        }catch(Exception ex){
            ex.printStackTrace();
            return -1;
        }
    }

    public int getNonRuntimeCountNodeSet(String xpath){
        try{
//            int value = -1;
            Document d = getVerifierContext().getDocument();
            if (d==null)
                return -1;
            XObject result = XPathAPI.eval(d, xpath,
                             (PrefixResolver)new XpathPrefixResolver (d));
            NodeList nl = result.nodelist();
            NodeSet ns = new NodeSet(nl);
            return ns.getLength();
        }catch(Exception ex){
            ex.printStackTrace();
            return -1;
        }
    }

    /*
     *getRuntimeSpecVersion()
     *   return Float Spec-version
     */
    public Float getRuntimeSpecVersion(){
        String docType = null;
        String versionStr = null;
        Float versionFloat=null;
        try{
            DocumentType dt = getVerifierContext().getRuntimeDocument().getDoctype();
            if (dt==null) return null;
            docType = dt.getPublicId();
            StringTokenizer st = new StringTokenizer(docType, "//");
            while (st.hasMoreElements()) {
                String tmp = st.nextToken();
                if (tmp.startsWith("DTD")) {
                // this is the string we are interested in
                    StringTokenizer versionST = new StringTokenizer(tmp);
                    while (versionST.hasMoreElements()) {
                        versionStr = versionST.nextToken();
                        try {
                            versionFloat = Float.valueOf(versionStr);
                        } catch(NumberFormatException nfe) {
                        // ignore, this is just the other info of the publicID
                        }
                    }
                }
            }
            return versionFloat;
        }catch(Exception ex){
            //ex.printStackTrace();
            return null;
        }
    }

    /**
     * If the param string is of primitive type this method return that class
     * representation of the primitive type
     * @param param
     * @return Class representaion of the primitive type
     */
    public Class checkIfPrimitive(String param) {

        if (param.equals("int"))
            return int.class;
        if (param.equals("boolean"))
            return boolean.class;
        if (param.equals("float"))
            return float.class;
        if (param.equals("double"))
            return(double.class);
        if (param.equals("byte"))
            return byte.class;
        if (param.equals("long"))
            return long.class;
        if (param.equals("char"))
            return char.class;
        if (param.equals("short"))
            return short.class;
        if (param.equals("void"))
            return void.class;

        return null;
    }

    /** These methods are used to add details to the result. Since this
     *  Class is the base class of all the Ejb Tests, all the tests
     *  can use these methods. Currently only runtime tests are using them.
     */
    
    protected void addGoodDetails(Result result, ComponentNameConstructor compName) {
        // make sure that this message is added only once
        if(addedGood) return;
        addedGood = true;
	    result.addGoodDetails(smh.getLocalString("tests.componentNameConstructor",
				                                 "For [ {0} ]",
				                                 new Object[] {compName.toString()}));
    }
    
    protected void addErrorDetails(Result result, ComponentNameConstructor compName) {
        // make sure that this message is added only once
        if(addedError) return;
        addedError = true;
	    result.addErrorDetails(smh.getLocalString("tests.componentNameConstructor",
				                                  "For [ {0} ]",
				                                  new Object[] {compName.toString()}));
    }
    
    protected void addWarningDetails(Result result, ComponentNameConstructor compName) {
        // make sure that this message is added only once
        if(addedWarning) return;
        addedWarning = true;
	    result.addWarningDetails(smh.getLocalString("tests.componentNameConstructor",
				                                    "For [ {0} ]",
				                                    new Object[] {compName.toString()}));
    }
    
    protected void addNaDetails(Result result, ComponentNameConstructor compName) {
        // make sure that this message is added only once
        if(addedNa) return;
        addedNa = true;
	    result.addNaDetails(smh.getLocalString("tests.componentNameConstructor",
				                               "For [ {0} ]",
				                               new Object[] {compName.toString()}));
    }
}
