/*
 * @(#)CoverageLogger.java
 *
 * Copyright (C) 2002-2004 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.logger;

import java.io.InputStream;
import java.util.Properties;

import net.sourceforge.groboutils.codecoverage.v2.IChannelLogger;
import net.sourceforge.groboutils.codecoverage.v2.IChannelLoggerFactory;


/**
 * The singleton invoked at runtime to log each marked bytecode instruction
 * covered.
 * <P>
 * This class needs to be fast, efficient, thread-safe, and classloader-safe.
 * "Classloader-safe" means that it needs to be resiliant to multiple instances
 * of this class being loaded, and possibly interfering with each other.
 * <P>
 * As of 12-Feb-2003, this class loads up its properties from a property
 * file, in the same way that Log4J loads its properties.  It attempts to
 * load the property file "/grobocoverage.properties" from the system
 * resources.  If the file cannot be found, then a warning is displayed
 * to STDERR.
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @version   $Date: 2004/05/14 21:12:11 $
 * @since     December 15, 2002
 */
public final class CoverageLogger implements ICoverageLoggerConst
{
    private static final String PROP_FILE_NAME = "grobocoverage.properties";
    private static final String PROP_FILE_RES = '/' + PROP_FILE_NAME;
    
    private static final boolean DEBUG = false;
    
    private static final String FACTORY_PROP = "factory";
    private static final String CHANNEL_COUNT_PROP = "channel-count";
    private static final String LOGGER_INIT_BASE_PROP = "logger.";
    private static final int DEFAULT_CHANNEL_COUNT = 0;
    private static final Class DEFAULT_CHANNEL_FACTORY =
            NoOpChannelLoggerFactory.class;

    // this must be defined before initBase(), and shouldn't set the loggers
    // to anything, due to the static processing order
    // of Java.  We avoid the null pointer checking by using the loggers count
    // field.
    private static IChannelLogger LOGGERS[];
    private static short LOGGERS_COUNT = (short)0;

    /**
     * Initialize the logger on class loading.
     */
    static {
        initBase();
    }
    
    
    
    
    /**
     * The primary entry method.  This must be lean, mean, thread-safe,
     * and classloader-safe.
     */
    public static final void cover( String classSig, short methodIndex,
            short channel, short markIndex )
    {
        if (channel >= 0 && channel < LOGGERS_COUNT)
        {
            //debug( "Logging ["+classSig+";"+methodIndex+":"+channel+
            //    "-"+markIndex+"]" );
            //error( "Logging ["+classSig+";"+methodIndex+":"+channel+
            //    "-"+markIndex+"]" );
            LOGGERS[ (int)channel ].cover( classSig, methodIndex, markIndex );
        }
        /*
        else
        {
            if (LOGGERS == null)
            {
                debug( "Couldn't log ["+classSig+";"+channel+":"+methodIndex+"."+
                    "-"+markIndex+"]: LOGGERS=null" );
            }
            else
            {
                debug( "Couldn't log ["+classSig+";"+channel+":"+methodIndex+"."+
                    "-"+markIndex+"]: LOGGERS length="+LOGGERS.length );
            }
        }
        */
    }
    
    
    /**
     * Initializes or reinitializes the static logger object based on the
     * logger property file, which will be used as the argument to
     * <tt>init( Properties )</tt>.  If no such file is found, then a
     * warning is reported to STDERR, and the System properties are passed
     * into the <tt>init</tt> method.
     */
    public static final void initBase()
    {
        // make sure we set the loggers count to 0 before we get started.
        // this way, we don't have to deal with LOGGERS null checking.
        LOGGERS_COUNT = (short)0;
        
        Properties props = null;
        try
        {
            InputStream is = CoverageLogger.class.getResourceAsStream(
                PROP_FILE_RES );
            if (is == null)
            {
                is = ClassLoader.getSystemResourceAsStream( PROP_FILE_NAME );
            }
            if (is != null)
            {
                debug( "Loading "+PROP_FILE_NAME );
                props = new Properties();
                props.load( is );
            }
            else
            {
                error( "No resource named "+PROP_FILE_NAME+"." );
            }
        }
        catch (ThreadDeath td)
        {
            // never catch these
            throw td;
        }
        catch (Throwable t)
        {
            error( t.toString() );
            // ignore
        }
        
        if (props == null)
        {
            props = System.getProperties();
            error( "Please create and/or add the file "+PROP_FILE_NAME+
                " to the classpath." );
            if (DEBUG)
            {
                System.exit(1);
            }
        }
        init( props );
    }
    
    
    /**
     * Initializes or reinitializes the static logger object with a specific
     * set of properties.
     *
     * @param props collection of properties used to discover the channel
     *     logger factory, and to initialize the new channel logger.
     */
    public static final void init( Properties props )
    {
        // start out by saying that there are no loggers.  This will only
        // change if we get to the end without errors.
        LOGGERS_COUNT = (short)0;
        
        if (props == null)
        {
            error( "Encountered null properties instance." );
            return;
        }
        
        short channelCount = getChannelCount( props );
        IChannelLoggerFactory factory = getChannelLoggerFactory( props );
        if (factory != null)
        {
            // only assign the loggers at the end, after we've created
            // everything
            
            IChannelLogger ls[] = new IChannelLogger[ channelCount ];
            for (short i = 0; i < channelCount; ++i)
            {
                ls[ (int)i ] = factory.
                    createChannelLogger( LOGGER_INIT_BASE_PROP, props, i );
                debug( "Logger "+i+" = "+ls[(int)i] );
            }
            
            LOGGERS = ls;
            
        }
        // else keep the loggers list as it was.
        
        if (LOGGERS == null)
        {
            debug( "Logger list is null." );
            LOGGERS = new IChannelLogger[ 0 ];
        }
        
        LOGGERS_COUNT = (short)LOGGERS.length;
    }
    
    
    private static final short getChannelCount( Properties props )
    {
        short channelCount = DEFAULT_CHANNEL_COUNT;
        String countStr = props.getProperty( CHANNEL_COUNT_PROP );
        if (countStr != null && countStr.length() > 0)
        {
            try
            {
                // if this line throws an exception, it will not
                // disturb the default count.
                int i = Integer.parseInt( countStr );

                if (i < 0 || i > Short.MAX_VALUE)
                {
                    error( "Channel count is outside range [0.."+
                        Short.MAX_VALUE+"].  Using default." );
                }
                else
                {
                    // no exception was thrown, so assign the new count.
                    channelCount = (short)i;
                }
            }
            catch (NumberFormatException ex)
            {
                error( "Trouble translating channel count ('"+countStr+
                    "') to a number.  Using default." );
            }
        }
        debug( "Channel Count = "+channelCount );
        return channelCount;
    }
    
    
    private static final IChannelLoggerFactory getChannelLoggerFactory(
            Properties props )
    {
        String factoryClassName = props.getProperty( FACTORY_PROP );
        Class factoryClass = DEFAULT_CHANNEL_FACTORY;
        IChannelLoggerFactory factory = null;
        
        if (factoryClassName != null)
        {
            try
            {
                // if this line throws an exception, it will not
                // disturb the factoryClass object.
                Class c = Class.forName( factoryClassName );
                
                // no exception was thrown, so assign the new class.
                factoryClass = c;
            }
            catch (ThreadDeath td)
            {
                // never catch these
                throw td;
            }
            catch (Throwable t)
            {
                error( "Could not load factory class '"+factoryClassName+
                    "'.  Using default." );
            }
        }
        
        try
        {
            factory = (IChannelLoggerFactory)factoryClass.newInstance();
        }
        catch (ThreadDeath td)
        {
            // never catch these
            throw td;
        }
        catch (Throwable t)
        {
            error( "Couldn't create a new factory or cast it to a "+
                (IChannelLoggerFactory.class.getName())+
                ".  Not using a logger." );
        }
        
        debug( "Factory = "+factory );
        return factory;
    }

    
    
    private static final void error( String message )
    {
        System.err.println( CoverageLogger.class.getName()+": "+message );
    }
    
    
    private static final void debug( String message )
    {
        if (DEBUG)
        {
            System.out.println( "DEBUG ["+
                CoverageLogger.class.getName()+"]: "+message );
        }
    }
}

