/*
 * @(#)AnalysisModuleType.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.ant;

import java.util.Hashtable;

import net.sourceforge.groboutils.codecoverage.v2.IAnalysisModule;
import net.sourceforge.groboutils.codecoverage.v2.module.BranchCountMeasure;
import net.sourceforge.groboutils.codecoverage.v2.module.BytecodeCountMeasure;
import net.sourceforge.groboutils.codecoverage.v2.module.CallPairMeasure;
import net.sourceforge.groboutils.codecoverage.v2.module.FunctionMeasure;
import net.sourceforge.groboutils.codecoverage.v2.module.LineCountMeasure;

import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.types.DataType;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;


/**
 * Loads an analysis module.
 *
 * @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 18, 2002
 */
public class AnalysisModuleType extends DataType
{
    private static final Hashtable NAMES_TO_CLASSES = new Hashtable();
    static {
        NAMES_TO_CLASSES.put( "line", LineCountMeasure.class );
        NAMES_TO_CLASSES.put( "linecount", LineCountMeasure.class );
        
        NAMES_TO_CLASSES.put( "bytecode", BytecodeCountMeasure.class );
        
        NAMES_TO_CLASSES.put( "branch", BranchCountMeasure.class );
        
        NAMES_TO_CLASSES.put( "call", CallPairMeasure.class );
        NAMES_TO_CLASSES.put( "callpair", CallPairMeasure.class );
        NAMES_TO_CLASSES.put( "call-pair", CallPairMeasure.class );
        
        NAMES_TO_CLASSES.put( "function", FunctionMeasure.class );
        NAMES_TO_CLASSES.put( "method", FunctionMeasure.class );
    }
    
    public static final class TypeAttribute extends EnumeratedAttribute
    {
        private String[] types = { "line", "linecount", "bytecode",
            "branch", "call", "callpair", "call-pair", "function",
            "method" };
        public String[] getValues()
        {
            return this.types;
        }
    }
    
    private String moduleName;
    private String className;
    private Path classpath;
    private String resource;
    private String loaderId = null;
    private String classpathId = null;
    
    
    public void setName( String n )
    {
        this.moduleName = n;
    }
    
    
    public void setType( TypeAttribute n )
    {
        this.moduleName = n.getValue();
    }
    
    
    public void setClassName( String c )
    {
        this.className = c;
    }
    
    
    /**
     * Set the classpath to be used when searching for component being defined
     * 
     * @param classpath an Ant Path object containing the classpath.
     */
    public void setClasspath(Path classpath)
    {
        if (this.classpath == null)
        {
            this.classpath = classpath;
        }
        else
        {
            this.classpath.append( classpath );
        }
    }

    /**
     * Create the classpath to be used when searching for component being defined
     */ 
    public Path createClasspath()
    {
        if (this.classpath == null)
        {
            this.classpath = new Path( getProject() );
        }
        return this.classpath.createPath();
    }

    /**
     * reference to a classpath to use when loading the files.
     * To actually share the same loader, set loaderref as well
     */
    public void setClasspathRef( Reference r )
    {
        this.classpathId = r.getRefId();
        createClasspath().setRefid(r);
    }
    
    
    /**
     * Use the reference to locate the loader. If the loader is not
     * found, taskdef will use the specified classpath and register it
     * with the specified name.
     *     
     * This allow multiple taskdef/typedef to use the same class loader,
     * so they can be used together. It eliminate the need to
     * put them in the CLASSPATH.
     *
     * @since Ant 1.5
     */
    public void setLoaderRef( Reference r )
    {
        loaderId = r.getRefId();
    }
    
    
    
    
    
    
    /**
     * This method is guaranteed to never return <tt>null</tt>.
     */
    public IAnalysisModule getAnalysisModule()
            throws BuildException
    {
        if (isReference())
        {
            return getRef().getAnalysisModule();
        }
        
        
        Class c = null;
        if (this.moduleName != null)
        {
            c = (Class)NAMES_TO_CLASSES.get( this.moduleName.toLowerCase() );
            if (c == null)
            {
                throw new BuildException( "Unknown analysis module name '"+
                    this.moduleName+"'." );
            }
        }
        else
        if (this.className != null)
        {
            c = createClass( this.className );
            if (c == null)
            {
                throw new BuildException( "Could not find class "+
                    this.className+" from classpath." );
            }
        }
        else
        {
            throw new BuildException( "Never set either the 'name' "+
                "or the 'classname' attribute." );
        }
        
        try
        {
            return (IAnalysisModule)c.newInstance();
        }
        catch (Exception ex)
        {
            throw new BuildException(
                "Error creating analysis module of type "+
                c.getName()+".", ex );
        }
    }
    
    
    
    protected AnalysisModuleType getRef()
    {
        return (AnalysisModuleType)getCheckedRef(
            AnalysisModuleType.class, "analysismodule" );
    }
    
    
    /**
     * Stolen from o.a.t.a.taskdefs.Definer
     */
    private Class createClass( String classname )
            throws BuildException
    {
        // If a loader has been set ( either by loaderRef or magic property )
        if (loaderId != null)
        {
            Object reusedLoader = getProject().getReference( loaderId );
            if (reusedLoader != null)
            {
                if (reusedLoader instanceof AntClassLoader)
                {
                    try
                    {
                        return ((AntClassLoader)reusedLoader).
                            loadClass( classname );
                    }
                    catch (Exception e)
                    {
                        throw new BuildException( "Error loading class "+
                            classname, e );
                    }
                }
                // In future the reference object may be the <loader> type
                // if( reusedLoader instanceof Loader ) {
                //      return ((Loader)reusedLoader).getLoader(project);
                // }
            }
        }
       
        AntClassLoader al = null;
        if (classpath != null)
        {
            al = new AntClassLoader( getProject(), classpath, true );
        }
        else
        {
            al = new AntClassLoader( getProject(), Path.systemClasspath, true );
        }
        // need to load Task via system classloader or the new
        // task we want to define will never be a Task but always
        // be wrapped into a TaskAdapter.
        // al.addSystemPackageRoot("org.apache.tools.ant");


        // If the loader is new, record it for future uses by other
        // task/typedefs
        if (loaderId != null)
        {
            if (getProject().getReference(loaderId) == null)
            {
                getProject().addReference( loaderId, al );
            }
        }
        
        try
        {
            return al.loadClass( classname );
        }
        catch (Exception e)
        {
            throw new BuildException( "Error loading class "+classname, e );
        }
    }
}

