/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 */
package org.apache.tools.todo.taskdefs;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Date;
import java.util.Properties;
import org.apache.myrmidon.api.TaskException;
import org.apache.myrmidon.listeners.AbstractProjectListener;
import org.apache.myrmidon.listeners.LogEvent;
import org.apache.tools.todo.taskdefs.javac.DefaultCompilerAdapter;
import org.apache.tools.todo.taskdefs.javac.Javac;
import org.apache.tools.todo.types.DirectoryScanner;
import org.apache.myrmidon.framework.file.Path;
import org.apache.tools.todo.types.PathUtil;
import org.apache.antlib.java.JavaTask;

/**
 * Instruments Java classes with <a href="http://www.reliable-systems.com/tools/">
 * iContract</a> DBC preprocessor. <br/>
 * The task can generate a properties file for <a
 * href="http://hjem.sol.no/hellesoy/icontrol.html">iControl</a> , a graphical
 * user interface that lets you turn on/off assertions. iControl generates a
 * control file that you can refer to from this task using the controlfile
 * attribute. <p/>
 *
 * Thanks to Rainer Schmitz for enhancements and comments.
 *
 * @author <a href="mailto:aslak.hellesoy@bekk.no">Aslak Hellesøy</a> <p/>
 *
 *
 *      <tableborder="1" cellpadding="2" cellspacing="0">
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            <b>Attribute</b>
 *          </td>
 *
 *          <tdvalign="top">
 *            <b>Description</b>
 *          </td>
 *
 *          <tdalign="center" valign="top">
 *            <b>Required</b>
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            srcdir
 *          </td>
 *
 *          <tdvalign="top">
 *            Location of the java files.
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            Yes
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            instrumentdir
 *          </td>
 *
 *          <tdvalign="top">
 *            Indicates where the instrumented source files should go.
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            Yes
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            repositorydir
 *          </td>
 *
 *          <tdvalign="top">
 *            Indicates where the repository source files should go.
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            Yes
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            builddir
 *          </td>
 *
 *          <tdvalign="top">
 *            Indicates where the compiled instrumented classes should go.
 *            Defaults to the value of instrumentdir. </p> <em>NOTE:</em> Don't
 *            use the same directory for compiled instrumented classes and
 *            uninstrumented classes. It will break the dependency checking.
 *            (Classes will not be reinstrumented if you change them).
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            No
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            repositorybuilddir
 *          </td>
 *
 *          <tdvalign="top">
 *            Indicates where the compiled repository classes should go.
 *            Defaults to the value of repositorydir.
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            No
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            pre
 *          </td>
 *
 *          <tdvalign="top">
 *            Indicates whether or not to instrument for preconditions. Defaults
 *            to <code>true</code> unless controlfile is specified, in which
 *            case it defaults to <code>false</code>.
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            No
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            post
 *          </td>
 *
 *          <tdvalign="top">
 *            Indicates whether or not to instrument for postconditions.
 *            Defaults to <code>true</code> unless controlfile is specified, in
 *            which case it defaults to <code>false</code>.
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            No
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            invariant
 *          </td>
 *
 *          <tdvalign="top">
 *            Indicates whether or not to instrument for invariants. Defaults to
 *            <code>true</code> unless controlfile is specified, in which case
 *            it defaults to <code>false</code>.
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            No
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            failthrowable
 *          </td>
 *
 *          <tdvalign="top">
 *            The full name of the Throwable (Exception) that should be thrown
 *            when an assertion is violated. Defaults to <code>java.lang.Error</code>
 *
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            No
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            verbosity
 *          </td>
 *
 *          <tdvalign="top">
 *            Indicates the verbosity level of iContract. Any combination of
 *            <code>error*,warning*,note*,info*,progress*,debug*</code> (comma
 *            separated) can be used. Defaults to <code>error*</code>
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            No
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            quiet
 *          </td>
 *
 *          <tdvalign="top">
 *            Indicates if iContract should be quiet. Turn it off if many your
 *            classes extend uninstrumented classes and you don't want warnings
 *            about this. Defaults to <code>false</code>
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            No
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            updateicontrol
 *          </td>
 *
 *          <tdvalign="top">
 *            If set to true, it indicates that the properties file for iControl
 *            in the current directory should be updated (or created if it
 *            doesn't exist). Defaults to <code>false</code>.
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            No
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            controlfile
 *          </td>
 *
 *          <tdvalign="top">
 *            The name of the control file to pass to iContract. Consider using
 *            iControl to generate the file. Default is not to pass a file.
 *
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            Only if <code>updateicontrol=true</code>
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            classdir
 *          </td>
 *
 *          <tdvalign="top">
 *            Indicates where compiled (unistrumented) classes are located. This
 *            is required in order to properly update the icontrol.properties
 *            file, not for instrumentation.
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            Only if <code>updateicontrol=true</code>
 *          </td>
 *
 *        </tr>
 *
 *        <tr>
 *
 *          <tdvalign="top">
 *            targets
 *          </td>
 *
 *          <tdvalign="top">
 *            Name of the file that will be generated by this task, which lists
 *            all the classes that iContract will instrument. If specified, the
 *            file will not be deleted after execution. If not specified, a file
 *            will still be created, but it will be deleted after execution.
 *
 *          </td>
 *
 *          <tdvalign="top" align="center">
 *            No
 *          </td>
 *
 *        </tr>
 *
 *      </table>
 *      <p/>
 *
 *      <b>Note:</b> iContract will use the java compiler indicated by the
 *      project's <code>build.compiler</code> property. See documentation of the
 *      Javac task for more information. <p/>
 *
 *      Nested includes and excludes are also supported. <p>
 *
 *      <b>Example:</b> </p> <pre>
 * &lt;icontract
 *    srcdir="${build.src}"
 *    instrumentdir="${build.instrument}"
 *    repositorydir="${build.repository}"
 *    builddir="${build.instrclasses}"
 *    updateicontrol="true"
 *    classdir="${build.classes}"
 *    controlfile="control"
 *    targets="targets"
 *    verbosity="error*,warning*"
 *    quiet="true"
 * >
 *    &lt;classpath refid="compile-classpath"/>
 * &lt;/icontract>
 * </pre>
 */
public class IContract extends MatchingTask
{
    private final static String ICONTROL_PROPERTIES_HEADER =
        " You might want to set classRoot to point to your normal compilation class root directory.";

    /**
     * compiler to use for instrumenation
     */
    private String icCompiler = "javac";

    /**
     * temporary file with file names of all java files to be instrumented
     */
    private File targets = null;

    /**
     * will be set to true if any of the sourca files are newer than the
     * instrumented files
     */
    private boolean dirty = false;

    /**
     * set to true if the iContract jar is missing
     */
    private boolean iContractMissing = false;

    /**
     * source file root
     */
    private File srcDir = null;

    /**
     * instrumentation src root
     */
    private File instrumentDir = null;

    /**
     * instrumentation build root
     */
    private File buildDir = null;

    /**
     * repository src root
     */
    private File repositoryDir = null;

    /**
     * repository build root
     */
    private File repBuildDir = null;

    /**
     * classpath
     */
    private Path classpath = null;

    /**
     * The class of the Throwable to be thrown on failed assertions
     */
    private String failThrowable = "java.lang.Error";

    /**
     * The -v option
     */
    private String verbosity = "error*";

    /**
     * The -q option
     */
    private boolean quiet = false;

    /**
     * The -m option
     */
    private File controlFile = null;

    /**
     * Indicates whether or not to instrument for preconditions
     */
    private boolean pre = true;
    private boolean preModified = false;

    /**
     * Indicates whether or not to instrument for postconditions
     */
    private boolean post = true;
    private boolean postModified = false;

    /**
     * Indicates whether or not to instrument for invariants
     */
    private boolean invariant = true;
    private boolean invariantModified = false;

    /**
     * Indicates whether or not to instrument all files regardless of timestamp
     */
    // can't be explicitly set, is set if control file exists and is newer than any source file
    private boolean instrumentall = false;

    /**
     * Indicates the name of a properties file (intentionally for iControl)
     * where the classpath property should be updated.
     */
    private boolean updateIcontrol = false;

    /**
     * Regular compilation class root
     */
    private File classDir = null;

    /**
     * Sets the build directory for instrumented classes
     *
     * @param buildDir the build directory
     */
    public void setBuilddir( File buildDir )
    {
        this.buildDir = buildDir;
    }

    /**
     * Sets the class directory (uninstrumented classes)
     *
     * @param classDir The new Classdir value
     */
    public void setClassdir( File classDir )
    {
        this.classDir = classDir;
    }

    /**
     * Sets the classpath to be used for invocation of iContract.
     *
     * @param path The new Classpath value
     * @path the classpath
     */
    public void setClasspath( final Path path )
    {
        createClasspath().add( path );
    }

    /**
     * Sets the control file to pass to iContract.
     *
     * @param controlFile the control file
     */
    public void setControlfile( File controlFile )
    {
        if( !controlFile.exists() )
        {
            getContext().info( "WARNING: Control file " + controlFile.getAbsolutePath() + " doesn't exist. iContract will be run without control file." );
        }
        this.controlFile = controlFile;
    }

    /**
     * Sets the Throwable (Exception) to be thrown on assertion violation
     *
     * @param clazz the fully qualified Throwable class name
     */
    public void setFailthrowable( String clazz )
    {
        this.failThrowable = clazz;
    }

    /**
     * Sets the instrumentation directory
     *
     * @param instrumentDir the source directory
     */
    public void setInstrumentdir( File instrumentDir )
    {
        this.instrumentDir = instrumentDir;
        if( this.buildDir == null )
        {
            setBuilddir( instrumentDir );
        }
    }

    /**
     * Turns on/off invariant instrumentation
     *
     * @param invariant true turns it on
     */
    public void setInvariant( boolean invariant )
    {
        this.invariant = invariant;
        invariantModified = true;
    }

    /**
     * Turns on/off postcondition instrumentation
     *
     * @param post true turns it on
     */
    public void setPost( boolean post )
    {
        this.post = post;
        postModified = true;
    }

    /**
     * Turns on/off precondition instrumentation
     *
     * @param pre true turns it on
     */
    public void setPre( boolean pre )
    {
        this.pre = pre;
        preModified = true;
    }

    /**
     * Tells iContract to be quiet.
     *
     * @param quiet true if iContract should be quiet.
     */
    public void setQuiet( boolean quiet )
    {
        this.quiet = quiet;
    }

    /**
     * Sets the build directory for instrumented classes
     *
     * @param repBuildDir The new Repbuilddir value
     */
    public void setRepbuilddir( File repBuildDir )
    {
        this.repBuildDir = repBuildDir;
    }

    /**
     * Sets the build directory for repository classes
     *
     * @param repositoryDir the source directory
     */
    public void setRepositorydir( File repositoryDir )
    {
        this.repositoryDir = repositoryDir;
        if( this.repBuildDir == null )
        {
            setRepbuilddir( repositoryDir );
        }
    }

    /**
     * Sets the source directory
     *
     * @param srcDir the source directory
     */
    public void setSrcdir( File srcDir )
    {
        this.srcDir = srcDir;
    }

    /**
     * Sets the name of the file where targets will be written. That is the file
     * that tells iContract what files to process.
     *
     * @param targets the targets file name
     */
    public void setTargets( File targets )
    {
        this.targets = targets;
    }

    /**
     * Decides whether or not to update iControl properties file
     *
     * @param updateIcontrol true if iControl properties file should be updated
     */
    public void setUpdateicontrol( boolean updateIcontrol )
    {
        this.updateIcontrol = updateIcontrol;
    }

    /**
     * Sets the verbosity level of iContract. Any combination of
     * error*,warning*,note*,info*,progress*,debug* (comma separated) can be
     * used. Defaults to error*,warning*
     *
     * @param verbosity verbosity level
     */
    public void setVerbosity( String verbosity )
    {
        this.verbosity = verbosity;
    }

    /**
     * Creates a nested classpath element
     *
     * @return the nested classpath element
     */
    public Path createClasspath()
    {
        if( classpath == null )
        {
            classpath = new Path();
        }
        return classpath;
    }

    /**
     * Executes the task
     *
     * @exception org.apache.myrmidon.api.TaskException if the instrumentation fails
     */
    public void execute()
        throws TaskException
    {
        preconditions();
        scan();
        if( dirty )
        {

            // turn off assertions if we're using controlfile, unless they are not explicitly set.
            boolean useControlFile = ( controlFile != null ) && controlFile.exists();
            if( useControlFile && !preModified )
            {
                pre = false;
            }
            if( useControlFile && !postModified )
            {
                post = false;
            }
            if( useControlFile && !invariantModified )
            {
                invariant = false;
            }
            // issue warning if pre,post or invariant is used together with controlfile
            if( ( pre || post || invariant ) && controlFile != null )
            {
                getContext().info( "WARNING: specifying pre,post or invariant will override control file settings" );
            }


            // We want to be notified if iContract jar is missing. This makes life easier for the user
            // who didn't understand that iContract is a separate library (duh!)

            //addProjectListener( new IContractPresenceDetector() );

            // Prepare the directories for iContract. iContract will make them if they
            // don't exist, but for some reason I don't know, it will complain about the REP files
            // afterwards
            instrumentDir.mkdirs();
            buildDir.mkdirs();
            repositoryDir.mkdirs();

            // Set the classpath that is needed for regular Javac compilation
            Path baseClasspath = createClasspath();

            // Might need to add the core classes if we're not using Sun's Javac (like Jikes)
            String compiler = getContext().getProperty( "build.compiler" ).toString();
            ClasspathHelper classpathHelper = new ClasspathHelper( compiler );
            classpathHelper.modify( baseClasspath );

            // Create the classpath required to compile the sourcefiles BEFORE instrumentation
            Path beforeInstrumentationClasspath = new Path();
            beforeInstrumentationClasspath.add( baseClasspath );
            beforeInstrumentationClasspath.addLocation( srcDir );

            // Create the classpath required to compile the sourcefiles AFTER instrumentation
            Path afterInstrumentationClasspath = new Path();
            afterInstrumentationClasspath.add( baseClasspath );
            afterInstrumentationClasspath.addLocation( instrumentDir );
            afterInstrumentationClasspath.addLocation( repositoryDir );
            afterInstrumentationClasspath.addLocation( srcDir );
            afterInstrumentationClasspath.addLocation( buildDir );

            // Create the classpath required to automatically compile the repository files
            Path repositoryClasspath = new Path();
            repositoryClasspath.add( baseClasspath );
            repositoryClasspath.addLocation( instrumentDir );
            repositoryClasspath.addLocation( srcDir );
            repositoryClasspath.addLocation( repositoryDir );
            repositoryClasspath.addLocation( buildDir );

            // Create the classpath required for iContract itself
            Path iContractClasspath = new Path();
            iContractClasspath.add( baseClasspath );
            iContractClasspath.addLocation( new File(System.getProperty( "java.home" ) + File.separator + ".." + File.separator + "lib" + File.separator + "tools.jar" ) );
            iContractClasspath.addLocation( srcDir );
            iContractClasspath.addLocation( repositoryDir );
            iContractClasspath.addLocation( instrumentDir );
            iContractClasspath.addLocation( buildDir );

            // Create a forked java process
            JavaTask iContract = null;//(Java)getProject().createTask( "java" );
            iContract.setFork( true );
            iContract.setClassname( "com.reliablesystems.iContract.Tool" );
            iContract.setClasspath( iContractClasspath );

            // Build the arguments to iContract
            StringBuffer args = new StringBuffer();
            args.append( directiveString() );
            args.append( "-v" ).append( verbosity ).append( " " );
            args.append( "-b" ).append( "\"" ).append( icCompiler ).append( " -classpath " ).append( beforeInstrumentationClasspath ).append( "\" " );
            args.append( "-c" ).append( "\"" ).append( icCompiler ).append( " -classpath " ).append( afterInstrumentationClasspath ).append( " -d " ).append( buildDir ).append( "\" " );
            args.append( "-n" ).append( "\"" ).append( icCompiler ).append( " -classpath " ).append( repositoryClasspath ).append( "\" " );
            args.append( "-d" ).append( failThrowable ).append( " " );
            args.append( "-o" ).append( instrumentDir ).append( File.separator ).append( "@p" ).append( File.separator ).append( "@f.@e " );
            args.append( "-k" ).append( repositoryDir ).append( File.separator ).append( "@p " );
            args.append( quiet ? "-q " : "" );
            args.append( instrumentall ? "-a " : "" );// reinstrument everything if controlFile exists and is newer than any class
            args.append( "@" ).append( targets.getAbsolutePath() );
            iContract.createArg().setLine( args.toString() );

            //System.out.println( "JAVA -classpath " + iContractClasspath + " com.reliablesystems.iContract.Tool " + args.toString() );

            // update iControlProperties if it's set.
            if( updateIcontrol )
            {
                Properties iControlProps = new Properties();
                try
                {// to read existing propertiesfile
                    iControlProps.load( new FileInputStream( "icontrol.properties" ) );
                }
                catch( IOException e )
                {
                    getContext().info( "File icontrol.properties not found. That's ok. Writing a default one." );
                }
                iControlProps.setProperty( "sourceRoot", srcDir.getAbsolutePath() );
                iControlProps.setProperty( "classRoot", classDir.getAbsolutePath() );
                final String classpath = PathUtil.formatPath( afterInstrumentationClasspath, getContext() );
                iControlProps.setProperty( "classpath", classpath );
                iControlProps.setProperty( "controlFile", controlFile.getAbsolutePath() );
                iControlProps.setProperty( "targetsFile", targets.getAbsolutePath() );

                try
                {// to read existing propertiesfile
                    iControlProps.store( new FileOutputStream( "icontrol.properties" ), ICONTROL_PROPERTIES_HEADER );
                    getContext().info( "Updated icontrol.properties" );
                }
                catch( IOException e )
                {
                    getContext().info( "Couldn't write icontrol.properties." );
                }
            }

            // do it!
            iContract.executeJava();
        }
        else
        {// not dirty
            //log( "Nothing to do. Everything up to date." );
        }
    }

    /**
     * Creates the -m option based on the values of controlFile, pre, post and
     * invariant.
     *
     * @return Description of the Returned Value
     */
    private final String directiveString()
    {
        StringBuffer sb = new StringBuffer();
        boolean comma = false;

        boolean useControlFile = ( controlFile != null ) && controlFile.exists();
        if( useControlFile || pre || post || invariant )
        {
            sb.append( "-m" );
        }
        if( useControlFile )
        {
            sb.append( "@" ).append( controlFile );
            comma = true;
        }
        if( pre )
        {
            if( comma )
            {
                sb.append( "," );
            }
            sb.append( "pre" );
            comma = true;
        }
        if( post )
        {
            if( comma )
            {
                sb.append( "," );
            }
            sb.append( "post" );
            comma = true;
        }
        if( invariant )
        {
            if( comma )
            {
                sb.append( "," );
            }
            sb.append( "inv" );
        }
        sb.append( " " );
        return sb.toString();
    }

    /**
     * Checks that the required attributes are set.
     *
     * @exception org.apache.myrmidon.api.TaskException Description of Exception
     */
    private void preconditions()
        throws TaskException
    {
        if( srcDir == null )
        {
            throw new TaskException( "srcdir attribute must be set!" );
        }
        if( !srcDir.exists() )
        {
            throw new TaskException( "srcdir \"" + srcDir.getPath() + "\" does not exist!" );
        }
        if( instrumentDir == null )
        {
            throw new TaskException( "instrumentdir attribute must be set!" );
        }
        if( repositoryDir == null )
        {
            throw new TaskException( "repositorydir attribute must be set!" );
        }
        if( updateIcontrol == true && classDir == null )
        {
            throw new TaskException( "classdir attribute must be specified when updateicontrol=true!" );
        }
        if( updateIcontrol == true && controlFile == null )
        {
            throw new TaskException( "controlfile attribute must be specified when updateicontrol=true!" );
        }
    }

    /**
     * Verifies whether any of the source files have changed. Done by comparing
     * date of source/class files. The whole lot is "dirty" if at least one
     * source file or the control file is newer than the instrumented files. If
     * not dirty, iContract will not be executed. <br/>
     * Also creates a temporary file with a list of the source files, that will
     * be deleted upon exit.
     *
     * @exception org.apache.myrmidon.api.TaskException Description of Exception
     */
    private void scan()
        throws TaskException
    {
        long now = ( new Date() ).getTime();

        DirectoryScanner ds = null;

        ds = getDirectoryScanner( srcDir );
        String[] files = ds.getIncludedFiles();

        FileOutputStream targetOutputStream = null;
        PrintStream targetPrinter = null;
        boolean writeTargets = false;
        try
        {
            if( targets == null )
            {
                targets = new File( "targets" );
                getContext().info( "Warning: targets file not specified. generating file: " + targets.getName() );
                writeTargets = true;
            }
            else if( !targets.exists() )
            {
                getContext().info( "Specified targets file doesn't exist. generating file: " + targets.getName() );
                writeTargets = true;
            }
            if( writeTargets )
            {
                getContext().info( "You should consider using iControl to create a target file." );
                targetOutputStream = new FileOutputStream( targets );
                targetPrinter = new PrintStream( targetOutputStream );
            }
            for( int i = 0; i < files.length; i++ )
            {
                File srcFile = new File( srcDir, files[ i ] );
                if( files[ i ].endsWith( ".java" ) )
                {
                    // print the target, while we're at here. (Only if generatetarget=true).
                    if( targetPrinter != null )
                    {
                        targetPrinter.println( srcFile.getAbsolutePath() );
                    }
                    File classFile = new File( buildDir, files[ i ].substring( 0, files[ i ].indexOf( ".java" ) ) + ".class" );

                    if( srcFile.lastModified() > now )
                    {
                        final String message = "Warning: file modified in the future: " + files[ i ];
                        getContext().warn( message );
                    }

                    if( !classFile.exists() || srcFile.lastModified() > classFile.lastModified() )
                    {
                        //log( "Found a file newer than the instrumentDir class file: " + srcFile.getPath() + " newer than " + classFile.getPath() + ". Running iContract again..." );
                        dirty = true;
                    }
                }
            }
            if( targetPrinter != null )
            {
                targetPrinter.flush();
                targetPrinter.close();
            }
        }
        catch( IOException e )
        {
            throw new TaskException( "Could not create target file:" + e.getMessage() );
        }

        // also, check controlFile timestamp
        long controlFileTime = -1;
        try
        {
            if( controlFile != null )
            {
                if( controlFile.exists() && buildDir.exists() )
                {
                    controlFileTime = controlFile.lastModified();
                    ds = getDirectoryScanner( buildDir );
                    files = ds.getIncludedFiles();
                    for( int i = 0; i < files.length; i++ )
                    {
                        File srcFile = new File( srcDir, files[ i ] );
                        if( files[ i ].endsWith( ".class" ) )
                        {
                            if( controlFileTime > srcFile.lastModified() )
                            {
                                if( !dirty )
                                {
                                    getContext().info( "Control file " + controlFile.getAbsolutePath() + " has been updated. Instrumenting all files..." );
                                }
                                dirty = true;
                                instrumentall = true;
                            }
                        }
                    }
                }
            }
        }
        catch( Throwable t )
        {
            throw new TaskException( "Got an interesting exception:" + t.getMessage() );
        }
    }

    /**
     * This class is a helper to set correct classpath for other compilers, like
     * Jikes. It reuses the logic from DefaultCompilerAdapter, which is
     * protected, so we have to subclass it.
     *
     * @author RT
     */
    private class ClasspathHelper extends DefaultCompilerAdapter
    {
        private final String compiler;

        public ClasspathHelper( String compiler )
        {
            super();
            this.compiler = compiler;
        }

        // dummy implementation. Never called
        public void setJavac( Javac javac )
        {
        }

        public boolean execute()
        {
            return true;
        }

        // make it public
        public void modify( Path path )
            throws TaskException
        {
            // depending on what compiler to use, set the includeJavaRuntime flag
            if( "jikes".equals( compiler ) )
            {
                icCompiler = compiler;
                m_includeJavaRuntime = true;
                addCompileClasspath( path );
            }
        }
    }

    /**
     * BuildListener that sets the iContractMissing flag to true if a message
     * about missing iContract is missing. Used to indicate a more verbose error
     * to the user, with advice about how to solve the problem
     */
    private class IContractPresenceDetector
        extends AbstractProjectListener
    {
        /**
         * Notify listener of log message event.
         */
        public void log( final LogEvent event )
        {
            if( "java.lang.NoClassDefFoundError: com/reliablesystems/iContract/Tool".equals( event.getMessage() ) )
            {
                iContractMissing = true;
            }
        }
    }
}
