/*
 * @(#)ParseCoverageLogger.java
 *
 * Copyright (C) 2002-2003 Matt Albrecht
 * groboclown@users.sourceforge.net
 * http://groboutils.sourceforge.net
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 *  DEALINGS IN THE SOFTWARE.
 */

package net.sourceforge.groboutils.codecoverage.v2.compiler;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import net.sourceforge.groboutils.codecoverage.v2.logger.ICoverageLoggerConst;

import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.Type;

/**
 * Parses the CoverageLogger class to discover the BCEL compatible invocation
 * objects and names.
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @version   $Date: 2004/04/15 05:48:25 $
 * @since     December 17, 2002
 */
public class ParseCoverageLogger
{
    //private static final Class COVERAGELOGGER_CLASS = CoverageLogger.class;
    /**
     * This is not the simplest or safest way to default to the coverage
     * logger, but it does prevent loading the coverage logger class, thus
     * preventing a possible error message.
     */
    private static final String DEFAULT_COVERAGELOGGER_CLASSNAME =
        ICoverageLoggerConst.COVERAGELOGGER_CLASSNAME;
    private static final String DEFAULT_COVERAGELOGGER_METHODNAME =
        ICoverageLoggerConst.INVOKE_METHOD_NAME;
    private static final Class COVERAGE_SIGNATURE[] =
        ICoverageLoggerConst.COVERAGE_SIGNATURE;
    private static final Class COVERAGE_RETURNTYPE =
        ICoverageLoggerConst.COVERAGE_RETURNTYPE;
    
    private String methodSignature;
    private String methodName;
    private String className;
    
    
    /**
     * Reference the default CoverageLogger for creation.
     */
    public ParseCoverageLogger()
    {
        // We will assume that the defaults given are correct.  If they
        // aren't valid, then this is a major bug in the logger, not
        // this class.
        storeData( DEFAULT_COVERAGELOGGER_CLASSNAME,
            DEFAULT_COVERAGELOGGER_METHODNAME );
    }
    
    
    /**
     * Discovers the method signature and accompanying information for a
     * specific coverage logger to generate.  The given method must exist
     * with the correct parameters, attributes, and return type.
     * Otherwise, an exception is raised.
     */
    public ParseCoverageLogger( Class coverageClass, String methodName )
    {
        if (methodName == null || coverageClass == null)
        {
            throw new IllegalArgumentException( "No null args." );
        }
        parseCoverageLoggerType( coverageClass, methodName );
        
        storeData( coverageClass.getName(), methodName );
    }
    
    
    /**
     * Inner method to store the data of the class and method for the
     * logger.  It doesn't do any checking besides null.  Thus, this
     * method must be private to prevent inadvertent invalid class and
     * method assignment.
     */
    private void storeData( String className, String methodName )
    {
        this.methodName = methodName;
        this.className = className;
        if (methodName == null || className == null)
        {
            throw new IllegalArgumentException( "No null args." );
        }
        
        this.methodSignature = createSignature(
            COVERAGE_SIGNATURE, COVERAGE_RETURNTYPE );
        if (this.methodSignature == null)
        {
            throw new IllegalStateException(
                "Returned null method signature." );
        }
    }
    
    
    /**
     * Returns the fully-qualified class name for the CoverageLogger.
     */
    public String getClassName()
    {
        return this.className;
    }
    
    
    /**
     * Returns the name of the method, without the signature.
     */
    public String getMethodName()
    {
        return this.methodName;
    }
    
    
    /**
     * Returns the signature of the method, without the method name.
     */
    public String getMethodSignature()
    {
        return this.methodSignature;
    }
    
    
    private void parseCoverageLoggerType( Class clazz, String methodName )
    {
        // ensure that the method exists with the correct signature.
        Method m = null;
        try
        {
            m = clazz.getMethod( methodName, COVERAGE_SIGNATURE );
        }
        catch (NoSuchMethodException nsme)
        {
            // this will throw the next exception.  Hide this one!
            m = null;
        }
        
        if (m == null)
        {
            throw new IllegalStateException(
                clazz.getName()+" doesn't have a method named '"+
                methodName+"'" );
        }
        
        // the method MUST NOT return anything!
        if (m.getReturnType() != null &&
            !m.getReturnType().equals( COVERAGE_RETURNTYPE ))
        {
            throw new IllegalStateException(
                clazz.getName()+" method '"+
                methodName+"' incorrectly has a return value." );
        }
        
        // the method must also be static & public
        int mod = m.getModifiers();
        if (!Modifier.isStatic( mod ) || !Modifier.isPublic( mod ))
        {
            throw new IllegalStateException(
                clazz.getName()+" method '"+
                methodName+"' is not public or static." );
        }
    }
    
    
    private static String createSignature( Class signature[],
            Class returnClass )
    {
        Type returnType = getType( returnClass );
        Type args[] = new Type[ signature.length ];
        for (int i = 0; i < signature.length; ++i)
        {
            args[i] = getType( signature[i] );
        }
        return Type.getMethodSignature( returnType, args );
    }
    
    
    private static Type getType( Class c )
    {
        if (c.equals( Void.TYPE ))
        {
            return Type.VOID;
        }
        if (c.equals( Byte.TYPE ))
        {
            return Type.BYTE;
        }
        if (c.equals( Character.TYPE ))
        {
            return Type.CHAR;
        }
        if (c.equals( Short.TYPE ))
        {
            return Type.SHORT;
        }
        if (c.equals( Integer.TYPE ))
        {
            return Type.INT;
        }
        if (c.equals( Long.TYPE ))
        {
            return Type.LONG;
        }
        if (c.equals( Float.TYPE ))
        {
            return Type.FLOAT;
        }
        if (c.equals( Double.TYPE ))
        {
            return Type.DOUBLE;
        }
        // else
        return new ObjectType( c.getName() );
    }
}

