/*
 * @(#)CacheDirChannelLogger.java
 *
 * Copyright (C) 2003-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 net.sourceforge.groboutils.util.datastruct.v1.HashCache;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;


/**
 * This is the same as the DirectoryChannelLogger, except that the open
 * files are cached for access.
 * <P>
 * Besides some other issues with this (see open bugs), it can cause
 * issues if multiple loggers are trying to access the same file.  This
 * could happen if the CoverageLogger class is loaded by different class
 * loaders; as a result, multiple CoverageLogger instances will be created,
 * allowing for multiple channel loggers to write to the same area.
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @version   $Date: 2004/07/07 09:39:10 $
 * @since     May 9, 2003
 */
public class CacheDirChannelLogger extends DirectoryChannelLogger
        implements Runnable, HashCache.ObjectManager
{
    private final HashCache openedFiles;
    
    
    public CacheDirChannelLogger( File baseDir, int cacheSize )
    {
        super( baseDir );
        if (baseDir != null)
        {
            // even though the logic in the factory shouldn't allow this,
            // it apparently can happen.
            // See bug 923349
            if (cacheSize <= 0)
            {
                cacheSize = CacheDirChannelLoggerFactory.DEFAULT_CACHESIZE;
            }
            this.openedFiles = new HashCache( this, cacheSize );
            addShutdownHook();
        }
        else
        {
            this.openedFiles = null;
        }
    }
    
    
    /**
     * Records a coverage of a marked bytecode instruction.  This method should
     * never throw an exception.
     *
     * @param classSignature a signature of the class file being covered.
     *        this signature includes the fully-qualified name of the class,
     *        along with a checksum to uniquely identify it.
     * @param methodIndex index for a method within the class.  The meta-data
     *        store will know how to translate the index to a method signature.
     * @param markIndex the index of the bytecode instruction mark for this
     *        particular channel.
     */
    public void cover( String classSignature, short methodIndex,
            short markIndex )
    {
        if (this.baseDir != null)
        {
            File f = getClassFile( this.baseDir, classSignature );
            Writer w = null;
            try
            {
                char[] out = createCoverString( methodIndex, markIndex );
                synchronized (this)
                {
                    w = (Writer)this.openedFiles.get( f );
                    if (w != null)
                    {
                        w.write( out );
                    }
                }
            }
            catch (IOException ioe)
            {
                // gasp!!!!
                ioe.printStackTrace();
                
                // prevent these errors from occuring again
                this.baseDir = null;
            }
        }
    }
    
    
    /**
     * Called when the shutdown hook is called.
     */
    public void run()
    {
        synchronized( this )
        {
            this.openedFiles.clear();
        }
    }
    

    /**
     * Called by the HashCache
     */
    public Object createObject( Object key )
    {
        File f = (File)key;
        try
        {
            // bug 907800
            BufferedWriter bw = new BufferedWriter(
                new FileWriter( f.toString(), true ) );
            return bw;
        }
        catch (IOException ioe)
        {
            // gasp!!!!
            ioe.printStackTrace();
            
            // prevent these errors from occuring again
            this.baseDir = null;
            
            // we have to return something...
            return null;
        }
    }
    
    
    /**
     * Called by HashCache when the given object is being removed from the
     * cache.
     *
     * @param key the key associated with the object.
     * @param obj the object being cleaned up.
     */
    public void cleanUpObject( Object key, Object obj )
    {
        if (obj != null)
        {
            Writer w = (Writer)obj;
            try
            {
                w.flush();
                w.close();
            }
            catch (IOException ioe)
            {
                ioe.printStackTrace();
            }
        }
    }


    
    protected void finalize() throws Throwable
    {
        // our cleanup method is the "run" method.  Of course!
        run();
        
        super.finalize();
    }
    
    
    protected void addShutdownHook()
    {
        Class c = Runtime.class;
        try
        {
            java.lang.reflect.Method m = c.getMethod(
                "addShutdownHook", new Class[] { Thread.class } );
            Thread t = new Thread( this,
                this.getClass().getName()+" shutdown hook" );
            m.invoke( Runtime.getRuntime(), new Object[] { t } );
        }
        catch (Exception ex)
        {
            // prolly JDK 1.3 not supported.
            System.err.println(this.getClass().getName()+
                " should only be run in a JDK 1.3 compatible JVM.");
            ex.printStackTrace();
        }
    }
}

